From 78fc5d2feca60d3d957351a0c5279e7b922a1bd1 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 4 Jul 2020 18:11:57 -0400 Subject: [PATCH 01/68] List append symbol and partial implementation --- compiler/gen/src/llvm/build.rs | 64 ++++++++++++++++++++++++++++++++++ compiler/module/src/symbol.rs | 1 + 2 files changed, 65 insertions(+) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index e4cd1f6ffd..37faa18b57 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -1728,6 +1728,70 @@ fn call_with_args<'a, 'ctx, 'env>( } } } + Symbol::LIST_APPEND => { + // List.append : List elem, List elem -> List elem + debug_assert_eq!(args.len(), 2); + + let (first_list, first_list_layout) = &args[0]; + let (second_list, second_list_layout) = &args[1]; + + let builder = env.builder; + + let ctx = env.context; + + let first_wrapper_struct = first_list.into_struct_value(); + let second_wrapper_struct = second_list.into_struct_value(); + + let first_list_len = load_list_len(builder, first_wrapper_struct); + let second_list_len = load_list_len(builder, second_wrapper_struct); + + match first_list_layout { + Layout::Builtin(Builtin::List(elem_layout)) => match second_list_layout { + Layout::Builtin(Builtin::List(_)) => {} + + Layout::Builtin(Builtin::EmptyList) => { + let elem_type = + basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + clone_nonempty_list( + env, + first_list_len, + load_list_ptr(builder, first_wrapper_struct, ptr_type), + elem_layout, + ) + } + + _ => { + unreachable!("Invalid List layout for List.get: {:?}", second_list_layout); + } + }, + + Layout::Builtin(Builtin::EmptyList) => match second_list_layout { + Layout::Builtin(Builtin::List(elem_layout)) => { + let elem_type = + basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + clone_nonempty_list( + env, + second_list_len, + load_list_ptr(builder, second_wrapper_struct, ptr_type), + elem_layout, + ) + } + Layout::Builtin(Builtin::EmptyList) => empty_list(env), + _ => { + unreachable!("Invalid List layout for List.get: {:?}", second_list_layout); + } + }, + _ => { + unreachable!("Invalid List layout for List.get: {:?}", first_list_layout); + } + } + } Symbol::INT_DIV_UNSAFE => { debug_assert!(args.len() == 2); diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index 340839b03f..d03f9801ba 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -695,6 +695,7 @@ define_builtins! { 17 LIST_SINGLE: "single" 18 LIST_REPEAT: "repeat" 19 LIST_REVERSE: "reverse" + 20 LIST_APPEND: "append" } 7 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias From a5462b1043fbc6604c88719649c318f1109d5788 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Tue, 14 Jul 2020 21:50:35 -0400 Subject: [PATCH 02/68] Second loop in List append (non-functional) --- compiler/gen/src/llvm/build.rs | 282 ++++++++++++++++++++++++++++++--- compiler/gen/tests/gen_list.rs | 7 + 2 files changed, 265 insertions(+), 24 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 3a905cad80..1e613b1b86 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -1870,27 +1870,27 @@ fn run_low_level<'a, 'ctx, 'env>( empty_list(env) } Layout::Builtin(Builtin::List(elem_layout)) => { - let first_list_wrapper_struct = + let first_list_wrapper = build_expr(env, layout_ids, scope, parent, first_list).into_struct_value(); let builder = env.builder; let ctx = env.context; - let first_list_len = load_list_len(builder, first_list_wrapper_struct); + let first_list_len = load_list_len(builder, first_list_wrapper); // first_list_len > 0 // We do this check to avoid allocating memory. If the first input // list is empty, then we can just return the second list cloned - let first_list_empty_comparison = + let first_list_length_comparison = list_is_not_empty(builder, ctx, first_list_len); - let build_first_list_non_empty_then = || { + let build_first_list_then = || { let (second_list, second_list_layout) = &args[1]; - let second_list_wrapper_struct = + let second_list_wrapper = build_expr(env, layout_ids, scope, parent, second_list) .into_struct_value(); - let second_list_len = load_list_len(builder, second_list_wrapper_struct); + let second_list_len = load_list_len(builder, second_list_wrapper); match second_list_layout { Layout::Builtin(Builtin::EmptyList) => { @@ -1905,7 +1905,7 @@ fn run_low_level<'a, 'ctx, 'env>( let (new_wrapper, _) = clone_nonempty_list( env, first_list_len, - load_list_ptr(builder, first_list_wrapper_struct, ptr_type), + load_list_ptr(builder, first_list_wrapper, ptr_type), elem_layout, ); @@ -1915,18 +1915,256 @@ fn run_low_level<'a, 'ctx, 'env>( // second_list_len > 0 // We do this check to avoid allocating memory. If the second input // list is empty, then we can just return the first list cloned - let second_list_empty_comparison = + let second_list_length_comparison = list_is_not_empty(builder, ctx, second_list_len); - let build_second_list_non_empty_then = || {}; - let elem_type = basic_type_from_layout( - env.arena, - ctx, - elem_layout, - env.ptr_bytes, - ); + let build_second_list_then = || { + // Allocate space for the new array that we'll copy into. + let elem_type = basic_type_from_layout( + env.arena, + ctx, + elem_layout, + env.ptr_bytes, + ); - empty_list(env) + let combined_list_len = builder.build_int_add( + first_list_len, + second_list_len, + "add_list_lenghts", + ); + + let index_name = "#index"; + + // FIRST LOOP + let first_start_alloca = + builder.build_alloca(ctx.i64_type(), index_name); + + let first_index = ctx.i64_type().const_int(0, false); + builder.build_store(first_start_alloca, first_index); + + let first_loop_bb = + ctx.append_basic_block(parent, "first_list_append_loop"); + + builder.build_unconditional_branch(first_loop_bb); + builder.position_at_end(first_loop_bb); + + // #index = #index + 1 + let curr_first_index = builder + .build_load(first_start_alloca, index_name) + .into_int_value(); + let next_first_index = builder.build_int_add( + curr_first_index, + ctx.i64_type().const_int(1, false), + "nextindex", + ); + + builder.build_store(first_start_alloca, next_first_index); + + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + let first_list_ptr = + load_list_ptr(builder, first_list_wrapper, ptr_type); + + // The pointer to the element in the first list + let first_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + first_list_ptr, + &[curr_first_index], + "load_index", + ) + }; + + // The pointer to the element from the first list + // in the combined list + let first_combined_list_ptr = unsafe { + builder.build_in_bounds_gep( + first_list_ptr, + &[curr_first_index], + "load_index_reversed_list", + ) + }; + + let elem_from_first_list = + builder.build_load(first_list_elem_ptr, "get_elem"); + + // Mutate the new array in-place to change the element. + builder + .build_store(first_combined_list_ptr, elem_from_first_list); + + // #index < first_list_len + let first_loop_end_cond = builder.build_int_compare( + IntPredicate::ULT, + first_list_len, + curr_first_index, + "loopcond", + ); + + let after_first_loop_bb = + ctx.append_basic_block(parent, "after_first_loop"); + + builder.build_conditional_branch( + first_loop_end_cond, + first_loop_bb, + after_first_loop_bb, + ); + builder.position_at_end(after_first_loop_bb); + + // SECOND LOOP + let second_index_name = "#secondindex"; + let second_start_alloca = + builder.build_alloca(ctx.i64_type(), second_index_name); + + let second_index = ctx.i64_type().const_int(0, false); + builder.build_store(second_start_alloca, second_index); + + let second_loop_bb = + ctx.append_basic_block(parent, "second_list_append_loop"); + + builder.build_unconditional_branch(second_loop_bb); + builder.position_at_end(second_loop_bb); + + // #index = #index + 1 + let curr_second_index = builder + .build_load(second_start_alloca, second_index_name) + .into_int_value(); + let next_second_index = builder.build_int_add( + curr_second_index, + ctx.i64_type().const_int(1, false), + "nextindex", + ); + + builder.build_store(second_start_alloca, next_second_index); + + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + let second_list_ptr = + load_list_ptr(builder, second_list_wrapper, ptr_type); + + // The pointer to the element in the second list + let second_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + second_list_ptr, + &[curr_second_index], + "load_index", + ) + }; + + // The pointer to the element from the second list + // in the combined list + let second_combined_list_ptr = unsafe { + builder.build_in_bounds_gep( + second_list_ptr, + &[builder.build_int_add( + curr_second_index, + first_list_len, + "second_index_plus_first_list_len", + )], + "load_index_reversed_list", + ) + }; + + let elem_from_second_list = + builder.build_load(second_list_elem_ptr, "get_elem"); + + // Mutate the new array in-place to change the element. + builder.build_store( + second_combined_list_ptr, + elem_from_second_list, + ); + + // #index < second_list_len + let second_loop_end_cond = builder.build_int_compare( + IntPredicate::ULT, + second_list_len, + curr_second_index, + "loopcond", + ); + + let after_second_loop_bb = + ctx.append_basic_block(parent, "after_second_loop"); + + builder.build_conditional_branch( + second_loop_end_cond, + second_loop_bb, + after_second_loop_bb, + ); + builder.position_at_end(after_second_loop_bb); + + // END + let combined_list_ptr = env + .builder + .build_array_malloc( + elem_type, + combined_list_len, + "create_combined_list_ptr", + ) + .unwrap(); + + let ptr_bytes = env.ptr_bytes; + let int_type = ptr_int(ctx, ptr_bytes); + let ptr_as_int = builder.build_ptr_to_int( + combined_list_ptr, + int_type, + "list_cast_ptr", + ); + let struct_type = collection(ctx, ptr_bytes); + + let mut struct_val; + + // Store the pointer + struct_val = builder + .build_insert_value( + struct_type.get_undef(), + ptr_as_int, + Builtin::WRAPPER_PTR, + "insert_ptr", + ) + .unwrap(); + + // Store the length + struct_val = builder + .build_insert_value( + struct_val, + combined_list_len, + Builtin::WRAPPER_LEN, + "insert_len", + ) + .unwrap(); + + builder.build_bitcast( + struct_val.into_struct_value(), + collection(ctx, ptr_bytes), + "cast_collection", + ) + }; + + let build_second_list_else = || { + let elem_type = basic_type_from_layout( + env.arena, + ctx, + elem_layout, + env.ptr_bytes, + ); + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + let (new_wrapper, _) = clone_nonempty_list( + env, + first_list_len, + load_list_ptr(builder, first_list_wrapper, ptr_type), + elem_layout, + ); + + BasicValueEnum::StructValue(new_wrapper) + }; + + build_basic_phi2( + env, + parent, + second_list_length_comparison, + build_second_list_then, + build_second_list_else, + BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), + ) } _ => { unreachable!( @@ -1937,17 +2175,13 @@ fn run_low_level<'a, 'ctx, 'env>( } }; - let struct_type = collection(ctx, env.ptr_bytes); - - let build_first_list_non_empty_else = || empty_list(env); - build_basic_phi2( env, parent, - first_list_empty_comparison, - build_first_list_non_empty_then, - build_first_list_non_empty_else, - BasicTypeEnum::StructType(struct_type), + first_list_length_comparison, + build_first_list_then, + || empty_list(env), + BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), ) } _ => { diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index e26efa2f9e..0ca295dda2 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -129,6 +129,13 @@ mod gen_list { fn list_append() { assert_evals_to!("List.append [] []", &[], &'static [i64]); assert_evals_to!("List.append [ 12, 13 ] []", &[12, 13], &'static [i64]); + assert_evals_to!( + "List.append [ 34 ] [ 44, 55 ]", + &[34, 44, 55], + &'static [i64] + ); + + // assert_evals_to!("List.append [] [ 23, 24 ]", &[23, 24], &'static [i64]); // assert_evals_to!( // "List.append [ 1, 2 ] [ 3, 4 ]", From c9869e67c111718ff2c21f55d8e59b9983fb0e10 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Tue, 14 Jul 2020 22:05:55 -0400 Subject: [PATCH 03/68] Regress to functional first loop, and no second loop --- compiler/gen/src/llvm/build.rs | 234 +++++++++++---------------------- 1 file changed, 76 insertions(+), 158 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 1e613b1b86..00ea0650c8 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -1933,164 +1933,6 @@ fn run_low_level<'a, 'ctx, 'env>( "add_list_lenghts", ); - let index_name = "#index"; - - // FIRST LOOP - let first_start_alloca = - builder.build_alloca(ctx.i64_type(), index_name); - - let first_index = ctx.i64_type().const_int(0, false); - builder.build_store(first_start_alloca, first_index); - - let first_loop_bb = - ctx.append_basic_block(parent, "first_list_append_loop"); - - builder.build_unconditional_branch(first_loop_bb); - builder.position_at_end(first_loop_bb); - - // #index = #index + 1 - let curr_first_index = builder - .build_load(first_start_alloca, index_name) - .into_int_value(); - let next_first_index = builder.build_int_add( - curr_first_index, - ctx.i64_type().const_int(1, false), - "nextindex", - ); - - builder.build_store(first_start_alloca, next_first_index); - - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - - let first_list_ptr = - load_list_ptr(builder, first_list_wrapper, ptr_type); - - // The pointer to the element in the first list - let first_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - first_list_ptr, - &[curr_first_index], - "load_index", - ) - }; - - // The pointer to the element from the first list - // in the combined list - let first_combined_list_ptr = unsafe { - builder.build_in_bounds_gep( - first_list_ptr, - &[curr_first_index], - "load_index_reversed_list", - ) - }; - - let elem_from_first_list = - builder.build_load(first_list_elem_ptr, "get_elem"); - - // Mutate the new array in-place to change the element. - builder - .build_store(first_combined_list_ptr, elem_from_first_list); - - // #index < first_list_len - let first_loop_end_cond = builder.build_int_compare( - IntPredicate::ULT, - first_list_len, - curr_first_index, - "loopcond", - ); - - let after_first_loop_bb = - ctx.append_basic_block(parent, "after_first_loop"); - - builder.build_conditional_branch( - first_loop_end_cond, - first_loop_bb, - after_first_loop_bb, - ); - builder.position_at_end(after_first_loop_bb); - - // SECOND LOOP - let second_index_name = "#secondindex"; - let second_start_alloca = - builder.build_alloca(ctx.i64_type(), second_index_name); - - let second_index = ctx.i64_type().const_int(0, false); - builder.build_store(second_start_alloca, second_index); - - let second_loop_bb = - ctx.append_basic_block(parent, "second_list_append_loop"); - - builder.build_unconditional_branch(second_loop_bb); - builder.position_at_end(second_loop_bb); - - // #index = #index + 1 - let curr_second_index = builder - .build_load(second_start_alloca, second_index_name) - .into_int_value(); - let next_second_index = builder.build_int_add( - curr_second_index, - ctx.i64_type().const_int(1, false), - "nextindex", - ); - - builder.build_store(second_start_alloca, next_second_index); - - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - - let second_list_ptr = - load_list_ptr(builder, second_list_wrapper, ptr_type); - - // The pointer to the element in the second list - let second_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - second_list_ptr, - &[curr_second_index], - "load_index", - ) - }; - - // The pointer to the element from the second list - // in the combined list - let second_combined_list_ptr = unsafe { - builder.build_in_bounds_gep( - second_list_ptr, - &[builder.build_int_add( - curr_second_index, - first_list_len, - "second_index_plus_first_list_len", - )], - "load_index_reversed_list", - ) - }; - - let elem_from_second_list = - builder.build_load(second_list_elem_ptr, "get_elem"); - - // Mutate the new array in-place to change the element. - builder.build_store( - second_combined_list_ptr, - elem_from_second_list, - ); - - // #index < second_list_len - let second_loop_end_cond = builder.build_int_compare( - IntPredicate::ULT, - second_list_len, - curr_second_index, - "loopcond", - ); - - let after_second_loop_bb = - ctx.append_basic_block(parent, "after_second_loop"); - - builder.build_conditional_branch( - second_loop_end_cond, - second_loop_bb, - after_second_loop_bb, - ); - builder.position_at_end(after_second_loop_bb); - - // END let combined_list_ptr = env .builder .build_array_malloc( @@ -2100,6 +1942,82 @@ fn run_low_level<'a, 'ctx, 'env>( ) .unwrap(); + let index_name = "#index"; + + // FIRST LOOP + let start_alloca = + builder.build_alloca(ctx.i64_type(), index_name); + + let index = ctx.i64_type().const_int(0, false); + builder.build_store(start_alloca, index); + + let loop_bb = + ctx.append_basic_block(parent, "first_list_append_loop"); + + builder.build_unconditional_branch(loop_bb); + builder.position_at_end(loop_bb); + + // #index = #index + 1 + let curr_index = builder + .build_load(start_alloca, index_name) + .into_int_value(); + let next_index = builder.build_int_add( + curr_index, + ctx.i64_type().const_int(1, false), + "nextindex", + ); + + builder.build_store(start_alloca, next_index); + + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + let list_ptr = + load_list_ptr(builder, first_list_wrapper, ptr_type); + + // The pointer to the element in the first list + let first_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + list_ptr, + &[curr_index], + "load_index", + ) + }; + + // The pointer to the element from the first list + // in the combined list + let combined_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + combined_list_ptr, + &[curr_index], + "load_index_reversed_list", + ) + }; + + let elem = builder.build_load(first_list_elem_ptr, "get_elem"); + + // Mutate the new array in-place to change the element. + builder.build_store(combined_list_elem_ptr, elem); + + // #index < first_list_len + let loop_end_cond = builder.build_int_compare( + IntPredicate::ULT, + first_list_len, + curr_index, + "loopcond", + ); + + let after_loop_bb = + ctx.append_basic_block(parent, "after_first_loop"); + + builder.build_conditional_branch( + loop_end_cond, + loop_bb, + after_loop_bb, + ); + builder.position_at_end(after_loop_bb); + + // END + let ptr_bytes = env.ptr_bytes; let int_type = ptr_int(ctx, ptr_bytes); let ptr_as_int = builder.build_ptr_to_int( From 6ceacba99f5570fe0b5da315881ffff259f2d6bd Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 14 Jul 2020 08:20:04 -0400 Subject: [PATCH 04/68] Drop obsolete Arr.roc --- compiler/builtins/docs/Arr.roc | 264 --------------------------------- 1 file changed, 264 deletions(-) delete mode 100644 compiler/builtins/docs/Arr.roc diff --git a/compiler/builtins/docs/Arr.roc b/compiler/builtins/docs/Arr.roc deleted file mode 100644 index 5493c9bad8..0000000000 --- a/compiler/builtins/docs/Arr.roc +++ /dev/null @@ -1,264 +0,0 @@ -interface List - exposes [ List, map, fold ] - imports [] - -## Types - -## A sequential list of values. -## -## >>> [ 1, 2, 3 ] # a list of numbers -## -## >>> [ "a", "b", "c" ] # a list of strings -## -## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of floats -## -## The list [ 1, "a" ] gives an error, because each element in a list must have -## the same type. If you want to put a mix of #Int and #Str values into a list, try this: -## -## ``` -## mixedList : List [ IntElem Int, StrElem Str ]* -## mixedList = [ IntElem 1, IntElem 2, StrElem "a", StrElem "b" ] -## ``` -## -## The maximum size of a #List is limited by the amount of heap memory available -## to the current process. If there is not enough memory available, attempting to -## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html) -## is normally enabled, not having enough memory could result in the list appearing -## to be created just fine, but then crashing later.) -## -## > The theoretical maximum length for a list created in Roc is -## > #Int.highestUlen divided by 2. Attempting to create a list bigger than that -## > in Roc code will always fail, although in practice it is likely to fail -## > at much smaller lengths due to insufficient memory being available. -## -## ## Performance notes -## -## Under the hood, a list is a record containing a `len : Ulen` field as well -## as a pointer to a flat list of bytes. -## -## This is not a [persistent data structure](https://en.wikipedia.org/wiki/Persistent_data_structure), -## so copying it is not cheap! The reason #List is designed this way is because: -## -## * Copying small lists is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures are usually thin wrappers around flat lists anyway. They don't start conferring copying advantages until crossing a certain minimum size threshold. -## Many list operations are no faster with persistent data structures. For example, even if it were a persistent data structure, #List.map, #List.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch. -## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, #List.map, #List.keepIf, and #List.set can all be optimized to perform in-place mutations. -## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way #List worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using #List under the hood! -List elem : @List elem - -## Initialize - -single : elem -> List elem - -empty : List * - -repeat : elem, Ulen -> List elem - -range : Int a, Int a -> List (Int a) - -## TODO I don't think we should have this after all. *Maybe* have an Ok.toList instead? -## -## When given an #Err, returns #[], and when given #Ok, returns a list containing -## only that value. -## -## >>> List.fromResult (Ok 5) -## -## >>> List.fromResult (Err "the Err's contents get discarded") -## -## This is useful when using `List.joinMap` with a function ## that returns a #Result. -## -## > (List.joinMap [ "cat", "", "bat", "" ] Str.first) |> List.map List.fromResult -fromResult : Result elem * -> List elem - -## Transform - -reverse : List elem -> List elem - -sort : List elem, Sorter elem -> List elem - -## Convert each element in the list to something new, by calling a conversion -## function on each of them. Then return a new list of the converted values. -## -## > List.map [ 1, 2, 3 ] (\num -> num + 1) -## -## > List.map [ "", "a", "bc" ] Str.isEmpty -## -## `map` functions like this are common in Roc, and they all work similarly. -## See for example #Result.map, #Set.map, and #Map.map. -map : List before, (before -> after) -> List after - -## This works the same way as #List.map, except it also passes the index -## of the element to the conversion function. -indexedMap : List before, (before, Int -> after) -> List after - -## Add a single element to the end of a list. -## -## >>> List.append [ 1, 2, 3 ] 4 -## -## >>> [ 0, 1, 2 ] -## >>> |> List.append 3 -append : List elem, elem -> List elem - -## Add a single element to the beginning of a list. -## -## >>> List.prepend [ 1, 2, 3 ] 0 -## -## >>> [ 2, 3, 4 ] -## >>> |> List.prepend 1 -prepend : List elem, elem -> List elem - -## Put two lists together. -## -## >>> List.concat [ 1, 2, 3 ] [ 4, 5 ] -## -## >>> [ 0, 1, 2 ] -## >>> |> List.concat [ 3, 4 ] -concat : List elem, List elem -> List elem - -## Join the given lists together into one list. -## -## >>> List.join [ [ 1, 2, 3 ], [ 4, 5 ], [], [ 6, 7 ] ] -## -## >>> List.join [ [], [] ] -## -## >>> List.join [] -join : List (List elem) -> List elem - -joinMap : List before, (before -> List after) -> List after - -## Like #List.join, but only keeps elements tagged with `Ok`. Elements -## tagged with `Err` are dropped. -## -## This can be useful after using an operation that returns a #Result -## on each element of a list, for example #List.first: -## -## >>> [ [ 1, 2, 3 ], [], [], [ 4, 5 ] ] -## >>> |> List.map List.first -## >>> |> List.joinOks -joinOks : List (Result elem *) -> List elem - -## Iterates over the shortest of the given lists and returns a list of `Pair` -## tags, each wrapping one of the elements in that list, along with the elements -## in the same position in # the other lists. -## -## >>> List.zip [ "a1", "b1" "c1" ] [ "a2", "b2" ] [ "a3", "b3", "c3" ] -## -## Accepts up to 8 lists. -## -## > For a generalized version that returns whatever you like, instead of a `Pair`, -## > see `zipMap`. -zip : - List a, List b, -> List [ Pair a b ]* - List a, List b, List c, -> List [ Pair a b c ]* - List a, List b, List c, List d -> List [ Pair a b c d ]* - -## Like `zip` but you can specify what to do with each element. -## -## More specifically, [repeat what zip's docs say here] -## -## >>> List.zipMap [ 1, 2, 3 ] [ 0, 5, 4 ] [ 2, 1 ] \num1 num2 num3 -> num1 + num2 - num3 -## -## Accepts up to 8 lists. -zipMap : - List a, List b, (a, b) -> List c | - List a, List b, List c, (a, b, c) -> List d | - List a, List b, List c, List d, (a, b, c, d) -> List e - - -## Filter - -## Run the given function on each element of a list, and return all the -## elements for which the function returned `True`. -## -## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2) -## -## ## Performance Notes -## -## #List.keepIf always returns a list that takes up exactly the same amount -## of memory as the original, even if its length decreases. This is becase it -## can't know in advance exactly how much space it will need, and if it guesses a -## length that's too low, it would have to re-allocate. -## -## (If you want to do an operation like this which reduces the memory footprint -## of the resulting list, you can do two passes over the lis with #List.fold - one -## to calculate the precise new size, and another to populate the new list.) -## -## If given a unique list, #List.keepIf will mutate it in place to assemble the appropriate list. -## If that happens, this function will not allocate any new memory on the heap. -## If all elements in the list end up being kept, Roc will return the original -## list unaltered. -## -keepIf : List elem, (elem -> [True, False]) -> List elem - -## Run the given function on each element of a list, and return all the -## elements for which the function returned `False`. -## -## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2) -## -## ## Performance Notes -## -## #List.dropIf has the same performance characteristics as #List.keepIf. -## See its documentation for details on those characteristics! -dropIf : List elem, (elem -> [True, False]) -> List elem - -## Takes the requested number of elements from the front of a list -## and returns them. -## -## >>> take 5 [ 1, 2, 3, 4, 5, 6, 7, 8 ] -## -## If there are fewer elements in the list than the requeted number, -## returns the entire list. -## -## >>> take 5 [ 1, 2 ] -take : List elem, Int -> List elem - -## Drops the requested number of elements from the front of a list -## and returns the rest. -## -## >>> drop 5 [ 1, 2, 3, 4, 5, 6, 7, 8 ] -## -## If there are fewer elements in the list than the requested number to -## be dropped, returns an empty list. -## -## >>> drop 5 [ 1, 2 ] -drop : List elem, Int -> List elem - -## Access - -first : List elem -> [Ok elem, ListWasEmpty]* - -last : List elem -> [Ok elem, ListWasEmpty]* - -get : List elem, Ulen -> [Ok elem, OutOfBounds]* - -max : List (Num a) -> [Ok (Num a), ListWasEmpty]* - -min : List (Num a) -> [Ok (Num a), ListWasEmpty]* - -## Modify - -set : List elem, Ulen, elem -> List elem - -## Deconstruct - -split : List elem, Int -> { before: List elem, remainder: List elem } - -walk : List elem, { start : state, step : (state, elem -> state) } -> state - -walkBackwards : List elem, { start : state, step : (state, elem -> state) } -> state - -## Check - -## Returns the length of the list - the number of elements it contains. -## -## One #List can store up to 2,147,483,648 elements (just over 2 billion), which -## is exactly equal to the highest valid #I32 value. This means the #U32 this function -## returns can always be safely converted to an #I32 without losing any data. -len : List * -> Ulen - -isEmpty : List * -> Bool - -contains : List elem, elem -> Bool - -all : List elem, (elem -> Bool) -> Bool - -any : List elem, (elem -> Bool) -> Bool From b544218a479925d640d7eb108620cc404abfdf49 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 14 Jul 2020 08:20:36 -0400 Subject: [PATCH 05/68] Update some Num docs --- compiler/builtins/docs/Num.roc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 84dfc84c19..3c9816aa12 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -475,6 +475,23 @@ ceil : Float * -> Int * floor : Float * -> Int * trunc : Float * -> Int * +## Convert an #Int to a #Len. If the given number doesn't fit in #Len, it will be truncated. +## Since #Len has a different maximum number depending on the system you're building +## for, this may give a different answer on different systems. +## +## For example, on a 32-bit sytem, #Num.maxLen will return the same answer as +## #Num.maxU32. This means that calling `Num.toLen 9_000_000_000` on a 32-bit +## system will return #Num.maxU32 instead of 9 billion, because 9 billion is +## higher than #Num.maxU32 and will not fit in a #Len on a 32-bit system. +## +## However, calling `Num.toLen 9_000_000_000` on a 64-bit system will return +## the #Len value of 9_000_000_000. This is because on a 64-bit system, #Len can +## hold up to #Num.maxU64, and 9_000_000_000 is lower than #Num.maxU64. +## +## To convert a #Float to a #Len, first call either #Num.round, #Num.ceil, or #Num.floor +## on it, then call this on the resulting #Int. +toLen : Int * -> Len + ## Convert an #Int to an #I8. If the given number doesn't fit in #I8, it will be truncated. ## ## To convert a #Float to an #I8, first call either #Num.round, #Num.ceil, or #Num.floor @@ -484,6 +501,7 @@ toI16 : Int * -> I16 toI32 : Int * -> I32 toI64 : Int * -> I64 toI128 : Int * -> I128 + ## Convert an #Int to an #U8. If the given number doesn't fit in #U8, it will be truncated. ## Crashes if the given number is negative. toU8 : Int * -> U8 From e97b019ad20d7830c346b2f29919174e01fd95b2 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 14 Jul 2020 08:20:26 -0400 Subject: [PATCH 06/68] Add initial List.roc --- compiler/builtins/docs/List.roc | 444 ++++++++++++++++++++++++++++++++ 1 file changed, 444 insertions(+) create mode 100644 compiler/builtins/docs/List.roc diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc new file mode 100644 index 0000000000..cc0ed9d5d9 --- /dev/null +++ b/compiler/builtins/docs/List.roc @@ -0,0 +1,444 @@ +interface List + exposes [ List, map, fold ] + imports [] + +## Types + +## A sequential list of values. +## +## >>> [ 1, 2, 3 ] # a list of numbers +## +## >>> [ "a", "b", "c" ] # a list of strings +## +## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of floats +## +## The list [ 1, "a" ] gives an error, because each element in a list must have +## the same type. If you want to put a mix of #Int and #Str values into a list, try this: +## +## ``` +## mixedList : List [ IntElem Int, StrElem Str ]* +## mixedList = [ IntElem 1, IntElem 2, StrElem "a", StrElem "b" ] +## ``` +## +## The maximum size of a #List is limited by the amount of heap memory available +## to the current process. If there is not enough memory available, attempting to +## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html) +## is normally enabled, not having enough memory could result in the list appearing +## to be created just fine, but then crashing later.) +## +## > The theoretical maximum length for a list created in Roc is +## > #Int.highestLen divided by 2. Attempting to create a list bigger than that +## > in Roc code will always fail, although in practice it is likely to fail +## > at much smaller lengths due to insufficient memory being available. +## +## ## Performance Details +## +## Under the hood, a list is a record containing a `len : Len` field as well +## as a pointer to a flat list of bytes. +## +## This is not a [persistent data structure](https://en.wikipedia.org/wiki/Persistent_data_structure), +## so copying it is not cheap! The reason #List is designed this way is because: +## +## * Copying small lists is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures are usually thin wrappers around flat lists anyway. They don't start conferring copying advantages until crossing a certain minimum size threshold. +## Many list operations are no faster with persistent data structures. For example, even if it were a persistent data structure, #List.map, #List.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch. +## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, #List.map, #List.keepIf, and #List.set can all be optimized to perform in-place mutations. +## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way #List worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using #List under the hood! +List elem : @List elem + +## Initialize + +## A list with a single element in it. +## +## This is useful in pipelines, like so: +## +## websites = +## Str.concat domain ".com" +## |> List.single +## +single : elem -> List elem + +## An empty list. +empty : List * + +## Returns a list with the given length, where every element is the given value. +## +## +repeat : elem, Len -> List elem + +## Returns a list of all the integers between one and another, +## including both of the given numbers. +## +## >>> List.range 2 8 +range : Int a, Int a -> List (Int a) + +## Transform + +## Returns the list with its elements reversed. +## +## >>> List.reverse [ 1, 2, 3 ] +reverse : List elem -> List elem + +## Sorts a list using a function which specifies how two elements are ordered. +## +## +sort : List elem, (elem, elem -> [ Lt, Eq, Gt ]) -> List elem + +## Convert each element in the list to something new, by calling a conversion +## function on each of them. Then return a new list of the converted values. +## +## > List.map [ 1, 2, 3 ] (\num -> num + 1) +## +## > List.map [ "", "a", "bc" ] Str.isEmpty +## +## `map` functions like this are common in Roc, and they all work similarly. +## See for example #Result.map, #Set.map, and #Map.map. +map : List before, (before -> after) -> List after + +## This works the same way as #List.map, except it also passes the index +## of the element to the conversion function. +indexedMap : List before, (before, Int -> after) -> List after + +## Add a single element to the end of a list. +## +## >>> List.append [ 1, 2, 3 ] 4 +## +## >>> [ 0, 1, 2 ] +## >>> |> List.append 3 +append : List elem, elem -> List elem + +## Add a single element to the beginning of a list. +## +## >>> List.prepend [ 1, 2, 3 ] 0 +## +## >>> [ 2, 3, 4 ] +## >>> |> List.prepend 1 +prepend : List elem, elem -> List elem + +## Put two lists together. +## +## >>> List.concat [ 1, 2, 3 ] [ 4, 5 ] +## +## >>> [ 0, 1, 2 ] +## >>> |> List.concat [ 3, 4 ] +concat : List elem, List elem -> List elem + +## Join the given lists together into one list. +## +## >>> List.join [ [ 1, 2, 3 ], [ 4, 5 ], [], [ 6, 7 ] ] +## +## >>> List.join [ [], [] ] +## +## >>> List.join [] +join : List (List elem) -> List elem + +joinMap : List before, (before -> List after) -> List after + +## Like #List.join, but only keeps elements tagged with `Ok`. Elements +## tagged with `Err` are dropped. +## +## This can be useful after using an operation that returns a #Result +## on each element of a list, for example #List.first: +## +## >>> [ [ 1, 2, 3 ], [], [], [ 4, 5 ] ] +## >>> |> List.map List.first +## >>> |> List.joinOks +joinOks : List (Result elem *) -> List elem + +## Iterates over the shortest of the given lists and returns a list of `Pair` +## tags, each wrapping one of the elements in that list, along with the elements +## in the same position in # the other lists. +## +## >>> List.zip [ "a1", "b1" "c1" ] [ "a2", "b2" ] [ "a3", "b3", "c3" ] +## +## Accepts up to 8 lists. +## +## > For a generalized version that returns whatever you like, instead of a `Pair`, +## > see `zipMap`. +zip : + List a, List b, -> List [ Pair a b ]* + List a, List b, List c, -> List [ Pair a b c ]* + List a, List b, List c, List d -> List [ Pair a b c d ]* + +## Like `zip` but you can specify what to do with each element. +## +## More specifically, [repeat what zip's docs say here] +## +## >>> List.zipMap [ 1, 2, 3 ] [ 0, 5, 4 ] [ 2, 1 ] \num1 num2 num3 -> num1 + num2 - num3 +## +## Accepts up to 8 lists. +zipMap : + List a, List b, (a, b) -> List c | + List a, List b, List c, (a, b, c) -> List d | + List a, List b, List c, List d, (a, b, c, d) -> List e + + +## Filter + +## Run the given function on each element of a list, and return all the +## elements for which the function returned `True`. +## +## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2) +## +## ## Performance Details +## +## #List.keepIf always returns a list that takes up exactly the same amount +## of memory as the original, even if its length decreases. This is becase it +## can't know in advance exactly how much space it will need, and if it guesses a +## length that's too low, it would have to re-allocate. +## +## (If you want to do an operation like this which reduces the memory footprint +## of the resulting list, you can do two passes over the lis with #List.fold - one +## to calculate the precise new size, and another to populate the new list.) +## +## If given a unique list, #List.keepIf will mutate it in place to assemble the appropriate list. +## If that happens, this function will not allocate any new memory on the heap. +## If all elements in the list end up being kept, Roc will return the original +## list unaltered. +## +keepIf : List elem, (elem -> [True, False]) -> List elem + +## Run the given function on each element of a list, and return all the +## elements for which the function returned `False`. +## +## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2) +## +## ## Performance Details +## +## #List.dropIf has the same performance characteristics as #List.keepIf. +## See its documentation for details on those characteristics! +dropIf : List elem, (elem -> [True, False]) -> List elem + +## Takes the requested number of elements from the front of a list +## and returns them. +## +## >>> take 5 [ 1, 2, 3, 4, 5, 6, 7, 8 ] +## +## If there are fewer elements in the list than the requeted number, +## returns the entire list. +## +## >>> take 5 [ 1, 2 ] +take : List elem, Int -> List elem + +## Access + +## Returns the first element in the list, or `ListWasEmpty` if the list was empty. +first : List elem -> Result elem [ ListWasEmpty ]* + +## Returns the last element in the list, or `ListWasEmpty` if the list was empty. +last : List elem -> Result elem [ ListWasEmpty ]* + +## This takes a #Len because the maximum length of a #List is a #Len value, +## so #Len lets you specify any position up to the maximum length of +## the list. +get : List elem, Len -> Result elem [ OutOfBounds ]* + +max : List (Num a) -> Result (Num a) [ ListWasEmpty ]* + +min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* + +## Modify + +## This takes a #Len because the maximum length of a #List is a #Len value, +## so #Len lets you specify any position up to the maximum length of +## the list. +set : List elem, Len, elem -> List elem + +## Add a new element to the end of a list. +## +## Returns a new list with the given element as its last element. +## +## ## Performance Details +## +## When given a Unique list, this adds the new element in-place if possible. +## This is only possible if the list has enough capacity. Otherwise, it will +## have to *clone and grow*. See the section on [capacity](#capacity) in this +## module's documentation. +append : List elem, elem -> List elem + +## Add a new element to the beginning of a list. +## +## Returns a new list with the given element as its first element. +## +## ## Performance Details +## +## This always clones the entire list, even when given a Unique list. That means +## it runs about as fast as #List.addLast when both are given a Shared list. +## +## If you have a Unique list instead, #List.append will run much faster than +## #List.prepend except in the specific case where the list has no excess capacity, +## and needs to *clone and grow*. In that uncommon case, both #List.append and +## #List.prepend will run at about the same speed—since #List.prepend always +## has to clone and grow. +## +## | Unique list | Shared list | +##---------+--------------------------------+----------------+ +## append | in-place given enough capacity | clone and grow | +## prepend | clone and grow | clone and grow | +prepend : List elem, elem -> List elem + +## Remove the last element from the list. +## +## Returns both the removed element as well as the new list (with the removed +## element missing), or `Err ListWasEmpty` if the list was empty. +## +## Here's one way you can use this: +## +## when List.pop list is +## Ok { others, last } -> ... +## Err ListWasEmpty -> ... +## +## ## Performance Details +## +## Calling #List.pop on a Unique list runs extremely fast. It's essentially +## the same as a #List.last except it also returns the #List it was given, +## with its length decreased by 1. +## +## In contrast, calling #List.pop on a Shared list creates a new list, then +## copies over every element in the original list except the last one. This +## takes much longer. +dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpty ]* + +## +## Here's one way you can use this: +## +## when List.pop list is +## Ok { others, last } -> ... +## Err ListWasEmpty -> ... +## +## ## Performance Details +## +## When calling either #List.dropFirst or #List.dropLast on a Unique list, #List.dropLast +## runs *much* faster. This is because for #List.dropLast, removing the last element +## in-place is as easy as reducing the length of the list by 1. In contrast, +## removing the first element from the list involves copying every other element +## in the list into the position before it - which is massively more costly. +## +## In the case of a Shared list, +## +## | Unique list | Shared list | +##-----------+----------------------------------+---------------------------------+ +## dropFirst | #List.last + length change | #List.last + clone rest of list | +## dropLast | #List.last + clone rest of list | #List.last + clone rest of list | +dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmpty ]* + +## Drops the given number of elements from the end of the list. +## +## Returns a new list without the dropped elements. +## +## To remove elements from a list while also returning a list of the removed +## elements, use #List.split. +## +## To remove elements from the beginning of the list, use #List.dropFromFront. +## +## ## Performance Details +## +## When given a Unique list, this runs extremely fast. It subtracts the given +## number from the list's length (down to a minimum of 0) in-place, and that's it. +## +## In fact, `List.drop 1 list` runs faster than `List.dropLast list` when given +## a Unique list, because #List.dropLast returns the element it dropped - +## which introduces a conditional bounds check as well as a memory load. +drop : List elem, Len -> List elem + +## Drops the given number of elements from the front of the list. +## +## Returns a new list without the dropped elements. +## +## To remove elements from a list while also returning a list of the removed +## elements, use #List.split. +## +## ## Performance Details +## +## When given a Unique list, this runs extremely fast. It subtracts the given +## number from the list's length (down to a minimum of 0) in-place, and that's it. +## +## In fact, `List.drop 1 list` runs faster than `List.dropLast list` when given +## a Unique list, because #List.dropLast returns the element it dropped - +## which introduces a conditional bounds check as well as a memory load. +dropFromFront : List elem, Len -> List elem + +## Deconstruct + +## Splits the list into two lists, around the given index. +## +## The returned lists are labeled `before` and `others`. The `before` list will +## contain all the elements whose index in the original list was **less than** +## than the given index, # and the `others` list will be all the others. (This +## means if you give an index of 0, the `before` list will be empty and the +## `others` list will have the same elements as the original list.) +split : List elem, Len -> { before: List elem, others: List elem } + +## Build a value using each element in the list. +## +## Starting with a given `state` value, this walks through each element in the +## list from first to last, running a given `step` function on that element +## which updates the `state`. It returns the final `state` at the end. +## +## You can use it in a pipeline: +## +## [ 2, 4, 8 ] +## |> List.walk { start: 0, step: Num.add } +## +## This returns 14 because: +## * `state` starts at 0 (because of `start: 0`) +## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`. +## +## Here is a table of how `state` changes as #List.walk walks over the elements +## `[ 2, 4, 8 ]` using #Num.add as its `step` function to determine the next `state`. +## +## `state` | `elem` | `step state elem` (`Num.add state elem`) +## --------+--------+----------------------------------------- +## 0 | | +## 0 | 2 | 2 +## 2 | 4 | 6 +## 6 | 8 | 14 +## +## So `state` goes through these changes: +## 1. `0` (because of `start: 0`) +## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1 +## +## [ 1, 2, 3 ] +## |> List.walk { start: 0, step: Num.sub } +## +## This returns -6 because +## +## Note that in other languages, `walk` is sometimes called `reduce`, +## `fold`, `foldLeft`, or `foldl`. +walk : List elem, { start : state, step : (state, elem -> state) } -> state + +## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`, +## `fold`, `foldRight`, or `foldr`. +walkBackwards : List elem, { start : state, step : (state, elem -> state ]) } -> state + +## Same as #List.walk, except you can stop walking early. +## +## ## Performance Details +## +## Compared to #List.walk, this can potentially visit fewer elements (which can +## improve performance) at the cost of making each step take longer. +## However, the added cost to each step is extremely small, and can easily +## be outweighed if it results in skipping even a small number of elements. +## +## As such, it is typically better for performance to use this over #List.walk +## if returning `Done` earlier than the last element is expected to be common. +walkUntil : List elem, { start : state, step : (state, elem -> [ Continue state, Done state ]) } -> state + +# Same as #List.walkBackwards, except you can stop walking early. +walkBackwardsUntil : List elem, { start : state, step : (state, elem -> [ Continue state, Done state ]) } -> state + +## Check + +## Returns the length of the list - the number of elements it contains. +## +## One #List can store up to 2,147,483,648 elements (just over 2 billion), which +## is exactly equal to the highest valid #I32 value. This means the #U32 this function +## returns can always be safely converted to an #I32 without losing any data. +len : List * -> Len + +isEmpty : List * -> Bool + +contains : List elem, elem -> Bool + +all : List elem, (elem -> Bool) -> Bool + +any : List elem, (elem -> Bool) -> Bool From 056c3362b5a5ad8869842c3df943df458eff4a65 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 11:04:57 -0400 Subject: [PATCH 07/68] Update List docs some more --- compiler/builtins/docs/List.roc | 135 +++++++++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 9 deletions(-) diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index cc0ed9d5d9..fc2a4b504e 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -5,8 +5,7 @@ interface List ## Types ## A sequential list of values. -## -## >>> [ 1, 2, 3 ] # a list of numbers +## # >>> [ 1, 2, 3 ] # a list of numbers ## ## >>> [ "a", "b", "c" ] # a list of strings ## @@ -34,13 +33,119 @@ interface List ## ## Performance Details ## ## Under the hood, a list is a record containing a `len : Len` field as well -## as a pointer to a flat list of bytes. +## as a pointer to a reference count and a flat array of bytes. Unique lists +## store a capacity #Len instead of a reference count. ## -## This is not a [persistent data structure](https://en.wikipedia.org/wiki/Persistent_data_structure), -## so copying it is not cheap! The reason #List is designed this way is because: +## ## Shared Lists ## -## * Copying small lists is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures are usually thin wrappers around flat lists anyway. They don't start conferring copying advantages until crossing a certain minimum size threshold. -## Many list operations are no faster with persistent data structures. For example, even if it were a persistent data structure, #List.map, #List.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch. +## Shared lists are [reference counted](https://en.wikipedia.org/wiki/Reference_counting). +## +## Each time a given list gets referenced, its reference count ("refcount" for short) +## gets incremented. Each time a list goes out of scope, its refcount count gets +## decremented. Once a refcount, has been decremented more times than it has been +## incremented, we know nothing is referencing it anymore, and the list's memory +## will be immediately freed. +## +## Let's look at an example. +## +## ratings = [ 5, 4, 3 ] +## +## { foo: ratings, bar: ratings } +## +## The first line binds the name `ratings` to the list `[ 5, 4, 3 ]`. The list +## begins with a refcount of 1, because so far only `ratings` is referencing it. +## +## The second line alters this refcount. `{ foo: ratings` references +## the `ratings` list, which will result in its refcount getting incremented +## from 0 to 1. Similarly, `bar: ratings }` also references the `ratings` list, +## which will result in its refcount getting incremented from 1 to 2. +## +## Let's turn this example into a function. +## +## getRatings = \first -> +## ratings = [ first, 4, 3 ] +## +## { foo: ratings, bar: ratings } +## +## getRatings 5 +## +## At the end of the `getRatings` function, when the record gets returned, +## the original `ratings =` binding has gone out of scope and is no longer +## accessible. (Trying to reference `ratings` outside the scope of the +## `getRatings` function would be an error!) +## +## Since `ratings` represented a way to reference the list, and that way is no +## longer accessible, the list's refcount gets decremented when `ratings` goes +## out of scope. It will decrease from 2 back down to 1. +## +## Putting these together, when we call `getRatings 5`, what we get back is +## a record with two fields, `foo`, and `bar`, each of which refers to the same +## list, and that list has a refcount of 1. +## +## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`: +## +## getRatings = \first -> +## ratings = [ first, 4, 3 ] +## +## { foo: ratings, bar: ratings } +## +## (getRatings 5).bar +## +## Now, when this expression returns, only the `bar` field of the record will +## be returned. This will mean that the `foo` field becomes inaccessible, causing +## the list's refcount to get decremented from 2 to 1. At this point, the list is back +## where it started: there is only 1 reference to it. +## +## Finally let's suppose the final line were changed to this: +## +## List.first (getRatings 5).bar +## +## This call to #List.first means that even the list in the `bar` field has become +## inaccessible. As such, this line will cause the list's refcount to get +## decremented all the way to 0. At that point, nothing is referencing the list +## anymore, and its memory will get freed. +## +## Things are different if this is a list of lists instead of a list of numbers. +## Let's look at a simpler example using #List.first - first with a list of numbers, +## and then with a list of lists, to see how they differ. +## +## Here's the example using a list of numbers. +## +## nums = [ 1, 2, 3, 4, 5, 6, 7 ] +## +## first = List.first nums +## last = List.last nums +## +## first +## +## It makes a list, calls #List.first and #List.last on it, and then returns `first`. +## +## Here's the equivalent code with a list of lists: +## +## lists = [ [ 1 ], [ 2, 3 ], [], [ 4, 5, 6, 7 ] ] +## +## first = List.first lists +## last = List.last lists +## +## first +## +## TODO explain how in the former example, when we go to free `nums` at the end, +## we can free it immediately because there are no other refcounts. However, +## in the case of `lists`, we have to iterate through the list and decrement +## the refcounts of each of its contained lists - because they, too, have +## refcounts! Importantly, beacuse the first element had its refcount incremented +## because the function returned `first`, that element will actually end up +## *not* getting freed at the end - but all the others will be. +## +## In the `lists` example, `lists = [ ... ]` also creates a list with an initial +## refcount of 1. Separately, it also creates several other lists - each with +## their own refcounts - to go inside that list. (The empty list at the end +## does not use heap memory, and thus has no refcount.) +## +## At the end, we once again call #List.first on the list, but this time +## +## * Copying small lists (64 elements or fewer) is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures tend to be thin wrappers around flat arrays anyway. They don't have any copying advantage until crossing a certain minimum size threshold. +## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, #List.map, #List.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch. These operations are all ## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, #List.map, #List.keepIf, and #List.set can all be optimized to perform in-place mutations. ## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way #List worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using #List under the hood! List elem : @List elem @@ -94,9 +199,21 @@ sort : List elem, (elem, elem -> [ Lt, Eq, Gt ]) -> List elem ## See for example #Result.map, #Set.map, and #Map.map. map : List before, (before -> after) -> List after -## This works the same way as #List.map, except it also passes the index +## This works like #List.map, except it also passes the index ## of the element to the conversion function. -indexedMap : List before, (before, Int -> after) -> List after +mapWithIndex : List before, (before, Int -> after) -> List after + +## This works like #List.map, except the given function can return `Drop` to +## drop the transformed element - or wrap it in `Keep` to keep it. +## +mapOrDrop : List before, (before -> [ Keep after, Drop ]) -> List after + +## This works like #List.map, except at any time you can return `Abort` to +## cancel the operation - or wrap the element in `Continue` to continue. +mapOrCancel : List before, (before -> [ Continue after, Cancel ]) -> Result (List after) MapCanceled + +## If all the elements in the list are #Ok, +checkOk : List (Result ok err) -> Result (List ok) err ## Add a single element to the end of a list. ## From 50f7636f92aa5587454f5c2cfaac8820b54d95c8 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 11:05:29 -0400 Subject: [PATCH 08/68] Use character codes, not keycodes, for backspace --- editor/src/lib.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 62c1fae962..2868a32112 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -131,10 +131,8 @@ fn run_event_loop() -> Result<(), Box> { match ch { '\u{8}' | '\u{7f}' => { // In Linux, we get a '\u{8}' when you press backspace, - // but in macOS we get '\u{7f}'. In both, we - // get a Back keydown event. Therefore, we use the - // Back keydown event and ignore these, resulting - // in a system that works in both Linux and macOS. + // but in macOS we get '\u{7f}'. + text_state.pop(); } '\u{e000}'..='\u{f8ff}' | '\u{f0000}'..='\u{ffffd}' @@ -240,12 +238,6 @@ fn handle_keydown( } match virtual_keycode { - Back => { - // Backspace deletes a character. - // In Linux, we get a Unicode character for backspace events - // (which is handled elsewhere), but on macOS we only get one of these. - text_state.pop(); - } Copy => { todo!("copy"); } From ca6881ab3e110e4147bb2456dc1f609875fbee1b Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 11:11:21 -0400 Subject: [PATCH 09/68] Change the type of List.mapOrCancel --- compiler/builtins/docs/List.roc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index fc2a4b504e..b52e728f32 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -208,9 +208,9 @@ mapWithIndex : List before, (before, Int -> after) -> List after ## mapOrDrop : List before, (before -> [ Keep after, Drop ]) -> List after -## This works like #List.map, except at any time you can return `Abort` to -## cancel the operation - or wrap the element in `Continue` to continue. -mapOrCancel : List before, (before -> [ Continue after, Cancel ]) -> Result (List after) MapCanceled +## This works like #List.map, except at any time you can return `Err` to +## cancel the operation and return an error, or `Ok` to continue. +mapOrCancel : List before, (before -> Result after err) -> Result (List after) err ## If all the elements in the list are #Ok, checkOk : List (Result ok err) -> Result (List ok) err From 74bd85afaa0bf411844d50845820577a5911984a Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 18 Jul 2020 14:42:11 -0400 Subject: [PATCH 10/68] Remove phi1 helper --- compiler/gen/src/llvm/build.rs | 36 ---------------------------------- 1 file changed, 36 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 00ea0650c8..081732e672 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -945,42 +945,6 @@ where phi.as_basic_value() } -fn build_basic_phi1<'a, 'ctx, 'env, PassFn>( - env: &Env<'a, 'ctx, 'env>, - parent: FunctionValue<'ctx>, - comparison: IntValue<'ctx>, - mut build_pass: PassFn, - ret_type: BasicTypeEnum<'ctx>, -) -> BasicValueEnum<'ctx> -where - PassFn: FnMut() -> BasicValueEnum<'ctx>, -{ - let builder = env.builder; - let context = env.context; - - // build blocks - let then_block = context.append_basic_block(parent, "then"); - let cont_block = context.append_basic_block(parent, "branchcont"); - - builder.build_conditional_branch(comparison, then_block, cont_block); - - // build then block - builder.position_at_end(then_block); - let then_val = build_pass(); - builder.build_unconditional_branch(cont_block); - - let then_block = builder.get_insert_block().unwrap(); - - // emit merge block - builder.position_at_end(cont_block); - - let phi = builder.build_phi(ret_type, "branch"); - - phi.add_incoming(&[(&then_val, then_block)]); - - phi.as_basic_value() -} - /// TODO could this be added to Inkwell itself as a method on BasicValueEnum? fn set_name(bv_enum: BasicValueEnum<'_>, name: &str) { match bv_enum { From a37f6d670b786fc6ba38cf8cfffc60f801cad3b5 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 18 Jul 2020 14:43:06 -0400 Subject: [PATCH 11/68] Re-implement loop on second input list to List.append, except use an offset pointer instead of doing second_index + first_list_len arithemtic, which is more performant --- compiler/gen/src/llvm/build.rs | 90 +++++++++++++++++++++++++++++----- compiler/gen/tests/gen_list.rs | 4 +- 2 files changed, 81 insertions(+), 13 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 081732e672..7b797e5734 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -1826,13 +1826,7 @@ fn run_low_level<'a, 'ctx, 'env>( let (first_list, first_list_layout) = &args[0]; match first_list_layout { - Layout::Builtin(Builtin::EmptyList) => { - // In practice, this code cannot be reached, because if a list - // did have the layout of `EmptyList`, then it would not make - // it here. But this code is still run, in the sense that this - // LLVM is still built this case. - empty_list(env) - } + Layout::Builtin(Builtin::EmptyList) => empty_list(env), Layout::Builtin(Builtin::List(elem_layout)) => { let first_list_wrapper = build_expr(env, layout_ids, scope, parent, first_list).into_struct_value(); @@ -1947,8 +1941,7 @@ fn run_low_level<'a, 'ctx, 'env>( ) }; - // The pointer to the element from the first list - // in the combined list + // The pointer to the element in the combined list let combined_list_elem_ptr = unsafe { builder.build_in_bounds_gep( combined_list_ptr, @@ -1965,8 +1958,8 @@ fn run_low_level<'a, 'ctx, 'env>( // #index < first_list_len let loop_end_cond = builder.build_int_compare( IntPredicate::ULT, - first_list_len, curr_index, + first_list_len, "loopcond", ); @@ -1980,8 +1973,83 @@ fn run_low_level<'a, 'ctx, 'env>( ); builder.position_at_end(after_loop_bb); - // END + // SECOND LOOP + let index = ctx.i64_type().const_int(0, false); + builder.build_store(start_alloca, index); + + let second_loop_bb = + ctx.append_basic_block(parent, "first_list_append_loop"); + + builder.build_unconditional_branch(second_loop_bb); + builder.position_at_end(second_loop_bb); + + // #index = #index + 1 + let curr_second_index = builder + .build_load(start_alloca, index_name) + .into_int_value(); + let next_second_index = builder.build_int_add( + curr_second_index, + ctx.i64_type().const_int(1, false), + "nextindex", + ); + + builder.build_store(start_alloca, next_second_index); + + let second_list_ptr = + load_list_ptr(builder, second_list_wrapper, ptr_type); + + // The pointer to the element in the first list + let second_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + second_list_ptr, + &[curr_second_index], + "load_index", + ) + }; + + let offset_combined_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + combined_list_ptr, + &[first_list_len], + "elem", + ) + }; + + // The pointer to the element from the second list + // in the combined list + let combined_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + offset_combined_list_elem_ptr, + &[curr_second_index], + "load_index_reversed_list", + ) + }; + + let elem = builder.build_load(second_list_elem_ptr, "get_elem"); + + // Mutate the new array in-place to change the element. + builder.build_store(combined_list_elem_ptr, elem); + + // #index < first_list_len + let second_loop_end_cond = builder.build_int_compare( + IntPredicate::ULT, + curr_second_index, + second_list_len, + "loopcond", + ); + + let second_after_loop_bb = + ctx.append_basic_block(parent, "after_first_loop"); + + builder.build_conditional_branch( + second_loop_end_cond, + second_loop_bb, + second_after_loop_bb, + ); + builder.position_at_end(second_after_loop_bb); + + // END let ptr_bytes = env.ptr_bytes; let int_type = ptr_int(ctx, ptr_bytes); let ptr_as_int = builder.build_ptr_to_int( diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index 0ca295dda2..1547c3c401 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -130,8 +130,8 @@ mod gen_list { assert_evals_to!("List.append [] []", &[], &'static [i64]); assert_evals_to!("List.append [ 12, 13 ] []", &[12, 13], &'static [i64]); assert_evals_to!( - "List.append [ 34 ] [ 44, 55 ]", - &[34, 44, 55], + "List.append [ 34, 43 ] [ 64, 55, 66 ]", + &[34, 43, 64, 55, 66], &'static [i64] ); From 9881c91e1fdb8487de7e99e7f3ee4cb1cea28722 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 18 Jul 2020 17:37:19 -0400 Subject: [PATCH 12/68] Pull some shared variables higher up --- compiler/gen/src/llvm/build.rs | 25 +++++++------------------ 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 7b797e5734..d46c849739 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -1850,16 +1850,15 @@ fn run_low_level<'a, 'ctx, 'env>( let second_list_len = load_list_len(builder, second_list_wrapper); + let elem_type = + basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + let elem_type = + basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + match second_list_layout { Layout::Builtin(Builtin::EmptyList) => { - let elem_type = basic_type_from_layout( - env.arena, - ctx, - elem_layout, - env.ptr_bytes, - ); - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - let (new_wrapper, _) = clone_nonempty_list( env, first_list_len, @@ -1877,14 +1876,6 @@ fn run_low_level<'a, 'ctx, 'env>( list_is_not_empty(builder, ctx, second_list_len); let build_second_list_then = || { - // Allocate space for the new array that we'll copy into. - let elem_type = basic_type_from_layout( - env.arena, - ctx, - elem_layout, - env.ptr_bytes, - ); - let combined_list_len = builder.build_int_add( first_list_len, second_list_len, @@ -1927,8 +1918,6 @@ fn run_low_level<'a, 'ctx, 'env>( builder.build_store(start_alloca, next_index); - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - let list_ptr = load_list_ptr(builder, first_list_wrapper, ptr_type); From dc6f0daeef8721f107375c7622f3cc49140a0464 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 18 Jul 2020 21:06:00 -0400 Subject: [PATCH 13/68] Organized list append code into named closures --- compiler/gen/src/llvm/build.rs | 684 ++++++++++++++++++--------------- compiler/gen/tests/gen_list.rs | 31 +- 2 files changed, 400 insertions(+), 315 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index d46c849739..30c70279d2 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -1819,315 +1819,7 @@ fn run_low_level<'a, 'ctx, 'env>( BasicTypeEnum::StructType(struct_type), ) } - ListAppend => { - // List.append : List elem, List elem -> List elem - debug_assert_eq!(args.len(), 2); - - let (first_list, first_list_layout) = &args[0]; - - match first_list_layout { - Layout::Builtin(Builtin::EmptyList) => empty_list(env), - Layout::Builtin(Builtin::List(elem_layout)) => { - let first_list_wrapper = - build_expr(env, layout_ids, scope, parent, first_list).into_struct_value(); - - let builder = env.builder; - let ctx = env.context; - - let first_list_len = load_list_len(builder, first_list_wrapper); - - // first_list_len > 0 - // We do this check to avoid allocating memory. If the first input - // list is empty, then we can just return the second list cloned - let first_list_length_comparison = - list_is_not_empty(builder, ctx, first_list_len); - - let build_first_list_then = || { - let (second_list, second_list_layout) = &args[1]; - let second_list_wrapper = - build_expr(env, layout_ids, scope, parent, second_list) - .into_struct_value(); - - let second_list_len = load_list_len(builder, second_list_wrapper); - - let elem_type = - basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - - let elem_type = - basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - - match second_list_layout { - Layout::Builtin(Builtin::EmptyList) => { - let (new_wrapper, _) = clone_nonempty_list( - env, - first_list_len, - load_list_ptr(builder, first_list_wrapper, ptr_type), - elem_layout, - ); - - BasicValueEnum::StructValue(new_wrapper) - } - Layout::Builtin(Builtin::List(_)) => { - // second_list_len > 0 - // We do this check to avoid allocating memory. If the second input - // list is empty, then we can just return the first list cloned - let second_list_length_comparison = - list_is_not_empty(builder, ctx, second_list_len); - - let build_second_list_then = || { - let combined_list_len = builder.build_int_add( - first_list_len, - second_list_len, - "add_list_lenghts", - ); - - let combined_list_ptr = env - .builder - .build_array_malloc( - elem_type, - combined_list_len, - "create_combined_list_ptr", - ) - .unwrap(); - - let index_name = "#index"; - - // FIRST LOOP - let start_alloca = - builder.build_alloca(ctx.i64_type(), index_name); - - let index = ctx.i64_type().const_int(0, false); - builder.build_store(start_alloca, index); - - let loop_bb = - ctx.append_basic_block(parent, "first_list_append_loop"); - - builder.build_unconditional_branch(loop_bb); - builder.position_at_end(loop_bb); - - // #index = #index + 1 - let curr_index = builder - .build_load(start_alloca, index_name) - .into_int_value(); - let next_index = builder.build_int_add( - curr_index, - ctx.i64_type().const_int(1, false), - "nextindex", - ); - - builder.build_store(start_alloca, next_index); - - let list_ptr = - load_list_ptr(builder, first_list_wrapper, ptr_type); - - // The pointer to the element in the first list - let first_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - list_ptr, - &[curr_index], - "load_index", - ) - }; - - // The pointer to the element in the combined list - let combined_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - combined_list_ptr, - &[curr_index], - "load_index_reversed_list", - ) - }; - - let elem = builder.build_load(first_list_elem_ptr, "get_elem"); - - // Mutate the new array in-place to change the element. - builder.build_store(combined_list_elem_ptr, elem); - - // #index < first_list_len - let loop_end_cond = builder.build_int_compare( - IntPredicate::ULT, - curr_index, - first_list_len, - "loopcond", - ); - - let after_loop_bb = - ctx.append_basic_block(parent, "after_first_loop"); - - builder.build_conditional_branch( - loop_end_cond, - loop_bb, - after_loop_bb, - ); - builder.position_at_end(after_loop_bb); - - // SECOND LOOP - - let index = ctx.i64_type().const_int(0, false); - builder.build_store(start_alloca, index); - - let second_loop_bb = - ctx.append_basic_block(parent, "first_list_append_loop"); - - builder.build_unconditional_branch(second_loop_bb); - builder.position_at_end(second_loop_bb); - - // #index = #index + 1 - let curr_second_index = builder - .build_load(start_alloca, index_name) - .into_int_value(); - let next_second_index = builder.build_int_add( - curr_second_index, - ctx.i64_type().const_int(1, false), - "nextindex", - ); - - builder.build_store(start_alloca, next_second_index); - - let second_list_ptr = - load_list_ptr(builder, second_list_wrapper, ptr_type); - - // The pointer to the element in the first list - let second_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - second_list_ptr, - &[curr_second_index], - "load_index", - ) - }; - - let offset_combined_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - combined_list_ptr, - &[first_list_len], - "elem", - ) - }; - - // The pointer to the element from the second list - // in the combined list - let combined_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - offset_combined_list_elem_ptr, - &[curr_second_index], - "load_index_reversed_list", - ) - }; - - let elem = builder.build_load(second_list_elem_ptr, "get_elem"); - - // Mutate the new array in-place to change the element. - builder.build_store(combined_list_elem_ptr, elem); - - // #index < first_list_len - let second_loop_end_cond = builder.build_int_compare( - IntPredicate::ULT, - curr_second_index, - second_list_len, - "loopcond", - ); - - let second_after_loop_bb = - ctx.append_basic_block(parent, "after_first_loop"); - - builder.build_conditional_branch( - second_loop_end_cond, - second_loop_bb, - second_after_loop_bb, - ); - builder.position_at_end(second_after_loop_bb); - - // END - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = builder.build_ptr_to_int( - combined_list_ptr, - int_type, - "list_cast_ptr", - ); - let struct_type = collection(ctx, ptr_bytes); - - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value( - struct_val, - combined_list_len, - Builtin::WRAPPER_LEN, - "insert_len", - ) - .unwrap(); - - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) - }; - - let build_second_list_else = || { - let elem_type = basic_type_from_layout( - env.arena, - ctx, - elem_layout, - env.ptr_bytes, - ); - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - - let (new_wrapper, _) = clone_nonempty_list( - env, - first_list_len, - load_list_ptr(builder, first_list_wrapper, ptr_type), - elem_layout, - ); - - BasicValueEnum::StructValue(new_wrapper) - }; - - build_basic_phi2( - env, - parent, - second_list_length_comparison, - build_second_list_then, - build_second_list_else, - BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), - ) - } - _ => { - unreachable!( - "Invalid List layout for List.get: {:?}", - first_list_layout - ); - } - } - }; - - build_basic_phi2( - env, - parent, - first_list_length_comparison, - build_first_list_then, - || empty_list(env), - BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), - ) - } - _ => { - unreachable!("Invalid List layout for List.get: {:?}", first_list_layout); - } - } - } + ListAppend => list_append(env, layout_ids, scope, parent, args), ListPush => { // List.push List elem, elem -> List elem debug_assert_eq!(args.len(), 2); @@ -2374,6 +2066,380 @@ fn build_int_binop<'a, 'ctx, 'env>( } } +fn list_append<'a, 'ctx, 'env>( + env: &Env<'a, 'ctx, 'env>, + layout_ids: &mut LayoutIds<'a>, + scope: &Scope<'a, 'ctx>, + parent: FunctionValue<'ctx>, + args: &[(Expr<'a>, Layout<'a>)], +) -> BasicValueEnum<'ctx> { + // List.append : List elem, List elem -> List elem + debug_assert_eq!(args.len(), 2); + + // This implementation is quite long, let me explain what is complicating it. Here are our + // contraints: + // + // constraint 1. lists might be empty because they have tje layout `EmptyList`, or they might + // be empty because they have a `List` layout, but happen to be empty, such as in this code: + // + // list : List Int + // list = + // [] + // + // So we have two sources of truth for emptiness. + // + // constraint 2. iterating over a non-empty list involves allocating memory for a index and + // a loop, and allocating memory is costly, so we dont want to even try to iterate over empty + // lists. + // + // Accounting for all the possibilities in the two contraints above gives us 9 code paths: + // + // first list EmptyList List(list) List(list) + // second list where list.length = 0 where list.length > 0 + // --------------------------------------------------------------------- + // EmptyList | [] | [] | clone(1st_list) | + // --------------------------------------------------------------------- + // List(list) | [] | [] | clone(1st_list) | + // where list.length = 0 | | | | + // --------------------------------------------------------------------- + // List(list) | clone(2nd_list) | clone(2nd_list) | 2nd_list ++ 1st_list | + // where list.length > 0 | | | | + // --------------------------------------------------------------------- + // + let builder = env.builder; + let ctx = env.context; + + let (first_list, first_list_layout) = &args[0]; + + let (second_list, second_list_layout) = &args[1]; + + let second_list_wrapper = + build_expr(env, layout_ids, scope, parent, second_list).into_struct_value(); + + let second_list_len = load_list_len(builder, second_list_wrapper); + + let if_first_list_is_empty = || { + match second_list_layout { + Layout::Builtin(Builtin::EmptyList) => empty_list(env), + Layout::Builtin(Builtin::List(elem_layout)) => { + // second_list_len > 0 + // We do this check to avoid allocating memory. If the second input + // list is empty, then we can just return the first list cloned + let second_list_length_comparison = + list_is_not_empty(builder, ctx, second_list_len); + + let build_second_list_then = || { + let elem_type = + basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + let (new_wrapper, _) = clone_nonempty_list( + env, + second_list_len, + load_list_ptr(builder, second_list_wrapper, ptr_type), + elem_layout, + ); + + BasicValueEnum::StructValue(new_wrapper) + }; + + let build_second_list_else = || empty_list(env); + build_basic_phi2( + env, + parent, + second_list_length_comparison, + build_second_list_then, + build_second_list_else, + BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), + ) + } + _ => { + unreachable!( + "Invalid List layout for second input list of List.append: {:?}", + second_list_layout + ); + } + } + }; + + match first_list_layout { + Layout::Builtin(Builtin::EmptyList) => if_first_list_is_empty(), + Layout::Builtin(Builtin::List(elem_layout)) => { + let first_list_wrapper = + build_expr(env, layout_ids, scope, parent, first_list).into_struct_value(); + + let first_list_len = load_list_len(builder, first_list_wrapper); + + // first_list_len > 0 + // We do this check to avoid allocating memory. If the first input + // list is empty, then we can just return the second list cloned + let first_list_length_comparison = list_is_not_empty(builder, ctx, first_list_len); + + let if_first_list_is_not_empty = || { + let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + + let if_second_list_is_empty = || { + let (new_wrapper, _) = clone_nonempty_list( + env, + first_list_len, + load_list_ptr(builder, first_list_wrapper, ptr_type), + elem_layout, + ); + + BasicValueEnum::StructValue(new_wrapper) + }; + + match second_list_layout { + Layout::Builtin(Builtin::EmptyList) => if_second_list_is_empty(), + Layout::Builtin(Builtin::List(_)) => { + // second_list_len > 0 + // We do this check to avoid allocating memory. If the second input + // list is empty, then we can just return the first list cloned + let second_list_length_comparison = + list_is_not_empty(builder, ctx, second_list_len); + + let build_second_list_then = || { + let combined_list_len = builder.build_int_add( + first_list_len, + second_list_len, + "add_list_lengths", + ); + + let combined_list_ptr = env + .builder + .build_array_malloc( + elem_type, + combined_list_len, + "create_combined_list_ptr", + ) + .unwrap(); + + let index_name = "#index"; + let index_alloca = builder.build_alloca(ctx.i64_type(), index_name); + + let first_loop = || { + builder + .build_store(index_alloca, ctx.i64_type().const_int(0, false)); + + let loop_bb = + ctx.append_basic_block(parent, "first_list_append_loop"); + + builder.build_unconditional_branch(loop_bb); + builder.position_at_end(loop_bb); + + // #index = #index + 1 + let curr_index = builder + .build_load(index_alloca, index_name) + .into_int_value(); + let next_index = builder.build_int_add( + curr_index, + ctx.i64_type().const_int(1, false), + "nextindex", + ); + + builder.build_store(index_alloca, next_index); + + let first_list_ptr = + load_list_ptr(builder, first_list_wrapper, ptr_type); + + // The pointer to the element in the first list + let elem_ptr = unsafe { + builder.build_in_bounds_gep( + first_list_ptr, + &[curr_index], + "load_index", + ) + }; + + // The pointer to the element in the combined list + let combined_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + combined_list_ptr, + &[curr_index], + "load_index_combined_list", + ) + }; + + let elem = builder.build_load(elem_ptr, "get_elem"); + + // Mutate the new array in-place to change the element. + builder.build_store(combined_list_elem_ptr, elem); + + // #index < first_list_len + let loop_end_cond = builder.build_int_compare( + IntPredicate::ULT, + curr_index, + first_list_len, + "loopcond", + ); + + let after_loop_bb = + ctx.append_basic_block(parent, "after_first_loop"); + + builder.build_conditional_branch( + loop_end_cond, + loop_bb, + after_loop_bb, + ); + builder.position_at_end(after_loop_bb); + }; + + let second_loop = || { + builder + .build_store(index_alloca, ctx.i64_type().const_int(0, false)); + + let loop_bb = + ctx.append_basic_block(parent, "second_list_append_loop"); + + builder.build_unconditional_branch(loop_bb); + builder.position_at_end(loop_bb); + + // #index = #index + 1 + let curr_index = builder + .build_load(index_alloca, index_name) + .into_int_value(); + let next_index = builder.build_int_add( + curr_index, + ctx.i64_type().const_int(1, false), + "nextindex", + ); + + builder.build_store(index_alloca, next_index); + + let second_list_ptr = + load_list_ptr(builder, second_list_wrapper, ptr_type); + + // The pointer to the element in the second list + let second_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + second_list_ptr, + &[curr_index], + "load_index", + ) + }; + + // The pointer to the element in the combined list. + // Note that the pointer does not start at the index + // 0, it starts at the index of first_list_len. + let combined_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + combined_list_ptr, + &[first_list_len], + "elem", + ) + }; + + // The pointer to the element from the second list + // in the combined list + let combined_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + combined_list_elem_ptr, + &[curr_index], + "load_index_combined_list", + ) + }; + + let elem = builder.build_load(second_list_elem_ptr, "get_elem"); + + // Mutate the new array in-place to change the element. + builder.build_store(combined_list_elem_ptr, elem); + + // #index < second_list_len + let loop_end_cond = builder.build_int_compare( + IntPredicate::ULT, + curr_index, + second_list_len, + "loopcond", + ); + + let after_loop_bb = + ctx.append_basic_block(parent, "after_second_loop"); + + builder.build_conditional_branch( + loop_end_cond, + loop_bb, + after_loop_bb, + ); + builder.position_at_end(after_loop_bb); + }; + + first_loop(); + second_loop(); + + let ptr_bytes = env.ptr_bytes; + let int_type = ptr_int(ctx, ptr_bytes); + let ptr_as_int = builder.build_ptr_to_int( + combined_list_ptr, + int_type, + "list_cast_ptr", + ); + let struct_type = collection(ctx, ptr_bytes); + + let mut struct_val; + + // Store the pointer + struct_val = builder + .build_insert_value( + struct_type.get_undef(), + ptr_as_int, + Builtin::WRAPPER_PTR, + "insert_ptr", + ) + .unwrap(); + + // Store the length + struct_val = builder + .build_insert_value( + struct_val, + combined_list_len, + Builtin::WRAPPER_LEN, + "insert_len", + ) + .unwrap(); + + builder.build_bitcast( + struct_val.into_struct_value(), + collection(ctx, ptr_bytes), + "cast_collection", + ) + }; + + build_basic_phi2( + env, + parent, + second_list_length_comparison, + build_second_list_then, + if_second_list_is_empty, + BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), + ) + } + _ => { + unreachable!( + "Invalid List layout for first input list of List.append: {:?}", + first_list_layout + ); + } + } + }; + + build_basic_phi2( + env, + parent, + first_list_length_comparison, + if_first_list_is_not_empty, + if_first_list_is_empty, + BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), + ) + } + _ => { + unreachable!("Invalid List layout for List.get: {:?}", first_list_layout); + } + } +} + fn build_float_binop<'a, 'ctx, 'env>( env: &Env<'a, 'ctx, 'env>, lhs: FloatValue<'ctx>, diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index 1547c3c401..0e81d08d9c 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -128,6 +128,25 @@ mod gen_list { #[test] fn list_append() { assert_evals_to!("List.append [] []", &[], &'static [i64]); + + assert_evals_to!( + indoc!( + r#" + firstList : List Int + firstList = + [] + + secondList : List Int + secondList = + [] + + List.append firstList secondList + "# + ), + &[], + &'static [i64] + ); + assert_evals_to!("List.append [ 12, 13 ] []", &[12, 13], &'static [i64]); assert_evals_to!( "List.append [ 34, 43 ] [ 64, 55, 66 ]", @@ -135,13 +154,13 @@ mod gen_list { &'static [i64] ); - // assert_evals_to!("List.append [] [ 23, 24 ]", &[23, 24], &'static [i64]); + assert_evals_to!("List.append [] [ 23, 24 ]", &[23, 24], &'static [i64]); - // assert_evals_to!( - // "List.append [ 1, 2 ] [ 3, 4 ]", - // &[1, 2, 3, 4], - // &'static [i64] - // ); + assert_evals_to!( + "List.append [ 1, 2 ] [ 3, 4 ]", + &[1, 2, 3, 4], + &'static [i64] + ); } #[test] From 04198c8895cddc7e44912165a30544a218db33ba Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 18 Jul 2020 21:24:19 -0400 Subject: [PATCH 14/68] Revert use of closure for if_first_list_is_empty due to memory problem --- compiler/gen/src/llvm/build.rs | 51 +++++++++++++++++++++++++++++++++- 1 file changed, 50 insertions(+), 1 deletion(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 30c70279d2..087cea3da7 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -2144,6 +2144,7 @@ fn list_append<'a, 'ctx, 'env>( }; let build_second_list_else = || empty_list(env); + build_basic_phi2( env, parent, @@ -2163,7 +2164,55 @@ fn list_append<'a, 'ctx, 'env>( }; match first_list_layout { - Layout::Builtin(Builtin::EmptyList) => if_first_list_is_empty(), + Layout::Builtin(Builtin::EmptyList) => { + match second_list_layout { + Layout::Builtin(Builtin::EmptyList) => empty_list(env), + Layout::Builtin(Builtin::List(elem_layout)) => { + // THIS IS A COPY AND PASTE + // All the code under the Layout::Builtin(Builtin::List()) match branch + // is the same as what is under `if_first_list_is_empty`. Re-using + // `if_first_list_is_empty` here however, creates memory problems. + + // second_list_len > 0 + // We do this check to avoid allocating memory. If the second input + // list is empty, then we can just return the first list cloned + let second_list_length_comparison = + list_is_not_empty(builder, ctx, second_list_len); + + let build_second_list_then = || { + let elem_type = + basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + let (new_wrapper, _) = clone_nonempty_list( + env, + second_list_len, + load_list_ptr(builder, second_list_wrapper, ptr_type), + elem_layout, + ); + + BasicValueEnum::StructValue(new_wrapper) + }; + + let build_second_list_else = || empty_list(env); + + build_basic_phi2( + env, + parent, + second_list_length_comparison, + build_second_list_then, + build_second_list_else, + BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), + ) + } + _ => { + unreachable!( + "Invalid List layout for second input list of List.append: {:?}", + second_list_layout + ); + } + } + } Layout::Builtin(Builtin::List(elem_layout)) => { let first_list_wrapper = build_expr(env, layout_ids, scope, parent, first_list).into_struct_value(); From bc1c676be4b52e3959342ed33de3e0c3fe6a5c91 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 18 Jul 2020 21:30:07 -0400 Subject: [PATCH 15/68] Fix english errors in documentation --- compiler/gen/src/llvm/build.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index 087cea3da7..cab294b1f6 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -2079,7 +2079,7 @@ fn list_append<'a, 'ctx, 'env>( // This implementation is quite long, let me explain what is complicating it. Here are our // contraints: // - // constraint 1. lists might be empty because they have tje layout `EmptyList`, or they might + // constraint 1. lists might be empty because they have the layout `EmptyList`, or they might // be empty because they have a `List` layout, but happen to be empty, such as in this code: // // list : List Int @@ -2092,7 +2092,7 @@ fn list_append<'a, 'ctx, 'env>( // a loop, and allocating memory is costly, so we dont want to even try to iterate over empty // lists. // - // Accounting for all the possibilities in the two contraints above gives us 9 code paths: + // Accounting for all the possibilities in the two constraints above gives us 9 code paths: // // first list EmptyList List(list) List(list) // second list where list.length = 0 where list.length > 0 From 5b80bc0ae32473a8ac647ba666e4eba9b37d849f Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 18 Jul 2020 21:30:24 -0400 Subject: [PATCH 16/68] Use correct error messages for unreachable layouts --- compiler/gen/src/llvm/build.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index cab294b1f6..913ae42dd9 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -2467,8 +2467,8 @@ fn list_append<'a, 'ctx, 'env>( } _ => { unreachable!( - "Invalid List layout for first input list of List.append: {:?}", - first_list_layout + "Invalid List layout for second input list of List.append: {:?}", + second_list_layout ); } } @@ -2484,7 +2484,10 @@ fn list_append<'a, 'ctx, 'env>( ) } _ => { - unreachable!("Invalid List layout for List.get: {:?}", first_list_layout); + unreachable!( + "Invalid List layout for first list in List.append : {:?}", + first_list_layout + ); } } } From cb92b477ea9b42c087cfa0c487201ecbac27a821 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 18 Jul 2020 21:50:03 -0400 Subject: [PATCH 17/68] Reverted fancy closure organization in List.append --- compiler/gen/src/llvm/build.rs | 286 +++++++++++++++++---------------- 1 file changed, 146 insertions(+), 140 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index fd416eb2aa..ecba86ea0f 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -2242,7 +2242,16 @@ fn list_append<'a, 'ctx, 'env>( }; match second_list_layout { - Layout::Builtin(Builtin::EmptyList) => if_second_list_is_empty(), + Layout::Builtin(Builtin::EmptyList) => { + let (new_wrapper, _) = clone_nonempty_list( + env, + first_list_len, + load_list_ptr(builder, first_list_wrapper, ptr_type), + elem_layout, + ); + + BasicValueEnum::StructValue(new_wrapper) + } Layout::Builtin(Builtin::List(_)) => { // second_list_len > 0 // We do this check to avoid allocating memory. If the second input @@ -2250,7 +2259,7 @@ fn list_append<'a, 'ctx, 'env>( let second_list_length_comparison = list_is_not_empty(builder, ctx, second_list_len); - let build_second_list_then = || { + let if_second_list_is_not_empty = || { let combined_list_len = builder.build_int_add( first_list_len, second_list_len, @@ -2269,154 +2278,151 @@ fn list_append<'a, 'ctx, 'env>( let index_name = "#index"; let index_alloca = builder.build_alloca(ctx.i64_type(), index_name); - let first_loop = || { - builder - .build_store(index_alloca, ctx.i64_type().const_int(0, false)); + // FIRST LOOP - let loop_bb = - ctx.append_basic_block(parent, "first_list_append_loop"); + builder.build_store(index_alloca, ctx.i64_type().const_int(0, false)); - builder.build_unconditional_branch(loop_bb); - builder.position_at_end(loop_bb); + let first_loop_bb = + ctx.append_basic_block(parent, "first_list_append_loop"); - // #index = #index + 1 - let curr_index = builder - .build_load(index_alloca, index_name) - .into_int_value(); - let next_index = builder.build_int_add( - curr_index, - ctx.i64_type().const_int(1, false), - "nextindex", - ); + builder.build_unconditional_branch(first_loop_bb); + builder.position_at_end(first_loop_bb); - builder.build_store(index_alloca, next_index); + // #index = #index + 1 + let curr_first_loop_index = builder + .build_load(index_alloca, index_name) + .into_int_value(); + let next_first_loop_index = builder.build_int_add( + curr_first_loop_index, + ctx.i64_type().const_int(1, false), + "nextindex", + ); - let first_list_ptr = - load_list_ptr(builder, first_list_wrapper, ptr_type); + builder.build_store(index_alloca, next_first_loop_index); - // The pointer to the element in the first list - let elem_ptr = unsafe { - builder.build_in_bounds_gep( - first_list_ptr, - &[curr_index], - "load_index", - ) - }; + let first_list_ptr = + load_list_ptr(builder, first_list_wrapper, ptr_type); - // The pointer to the element in the combined list - let combined_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - combined_list_ptr, - &[curr_index], - "load_index_combined_list", - ) - }; - - let elem = builder.build_load(elem_ptr, "get_elem"); - - // Mutate the new array in-place to change the element. - builder.build_store(combined_list_elem_ptr, elem); - - // #index < first_list_len - let loop_end_cond = builder.build_int_compare( - IntPredicate::ULT, - curr_index, - first_list_len, - "loopcond", - ); - - let after_loop_bb = - ctx.append_basic_block(parent, "after_first_loop"); - - builder.build_conditional_branch( - loop_end_cond, - loop_bb, - after_loop_bb, - ); - builder.position_at_end(after_loop_bb); + // The pointer to the element in the first list + let first_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + first_list_ptr, + &[curr_first_loop_index], + "load_index", + ) }; - let second_loop = || { - builder - .build_store(index_alloca, ctx.i64_type().const_int(0, false)); - - let loop_bb = - ctx.append_basic_block(parent, "second_list_append_loop"); - - builder.build_unconditional_branch(loop_bb); - builder.position_at_end(loop_bb); - - // #index = #index + 1 - let curr_index = builder - .build_load(index_alloca, index_name) - .into_int_value(); - let next_index = builder.build_int_add( - curr_index, - ctx.i64_type().const_int(1, false), - "nextindex", - ); - - builder.build_store(index_alloca, next_index); - - let second_list_ptr = - load_list_ptr(builder, second_list_wrapper, ptr_type); - - // The pointer to the element in the second list - let second_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - second_list_ptr, - &[curr_index], - "load_index", - ) - }; - - // The pointer to the element in the combined list. - // Note that the pointer does not start at the index - // 0, it starts at the index of first_list_len. - let combined_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - combined_list_ptr, - &[first_list_len], - "elem", - ) - }; - - // The pointer to the element from the second list - // in the combined list - let combined_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - combined_list_elem_ptr, - &[curr_index], - "load_index_combined_list", - ) - }; - - let elem = builder.build_load(second_list_elem_ptr, "get_elem"); - - // Mutate the new array in-place to change the element. - builder.build_store(combined_list_elem_ptr, elem); - - // #index < second_list_len - let loop_end_cond = builder.build_int_compare( - IntPredicate::ULT, - curr_index, - second_list_len, - "loopcond", - ); - - let after_loop_bb = - ctx.append_basic_block(parent, "after_second_loop"); - - builder.build_conditional_branch( - loop_end_cond, - loop_bb, - after_loop_bb, - ); - builder.position_at_end(after_loop_bb); + // The pointer to the element in the combined list + let combined_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + combined_list_ptr, + &[curr_first_loop_index], + "load_index_combined_list", + ) }; - first_loop(); - second_loop(); + let first_list_elem = + builder.build_load(first_list_elem_ptr, "get_elem"); + + // Mutate the new array in-place to change the element. + builder.build_store(combined_list_elem_ptr, first_list_elem); + + // #index < first_list_len + let first_loop_end_cond = builder.build_int_compare( + IntPredicate::ULT, + curr_first_loop_index, + first_list_len, + "loopcond", + ); + + let after_first_loop_bb = + ctx.append_basic_block(parent, "after_first_loop"); + + builder.build_conditional_branch( + first_loop_end_cond, + first_loop_bb, + after_first_loop_bb, + ); + builder.position_at_end(after_first_loop_bb); + + // SECOND LOOP + builder.build_store(index_alloca, ctx.i64_type().const_int(0, false)); + + let second_loop_bb = + ctx.append_basic_block(parent, "second_list_append_loop"); + + builder.build_unconditional_branch(second_loop_bb); + builder.position_at_end(second_loop_bb); + + // #index = #index + 1 + let curr_second_index = builder + .build_load(index_alloca, index_name) + .into_int_value(); + let next_second_index = builder.build_int_add( + curr_second_index, + ctx.i64_type().const_int(1, false), + "nextindex", + ); + + builder.build_store(index_alloca, next_second_index); + + let second_list_ptr = + load_list_ptr(builder, second_list_wrapper, ptr_type); + + // The pointer to the element in the second list + let second_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + second_list_ptr, + &[curr_second_index], + "load_index", + ) + }; + + // The pointer to the element in the combined list. + // Note that the pointer does not start at the index + // 0, it starts at the index of first_list_len. In that + // sense it is "offset". + let offset_combined_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + combined_list_ptr, + &[first_list_len], + "elem", + ) + }; + + // The pointer to the element from the second list + // in the combined list + let combined_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + offset_combined_list_elem_ptr, + &[curr_second_index], + "load_index_combined_list", + ) + }; + + let second_list_elem = + builder.build_load(second_list_elem_ptr, "get_elem"); + + // Mutate the new array in-place to change the element. + builder.build_store(combined_list_elem_ptr, second_list_elem); + + // #index < second_list_len + let second_loop_end_cond = builder.build_int_compare( + IntPredicate::ULT, + curr_second_index, + second_list_len, + "loopcond", + ); + + let after_second_loop_bb = + ctx.append_basic_block(parent, "after_second_loop"); + + builder.build_conditional_branch( + second_loop_end_cond, + second_loop_bb, + after_second_loop_bb, + ); + builder.position_at_end(after_second_loop_bb); let ptr_bytes = env.ptr_bytes; let int_type = ptr_int(ctx, ptr_bytes); @@ -2460,7 +2466,7 @@ fn list_append<'a, 'ctx, 'env>( env, parent, second_list_length_comparison, - build_second_list_then, + if_second_list_is_not_empty, if_second_list_is_empty, BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), ) From 6160a2b2f18809f7e931911fbece94154f8ce5c2 Mon Sep 17 00:00:00 2001 From: Chad Stearns Date: Sat, 18 Jul 2020 22:14:09 -0400 Subject: [PATCH 18/68] Moved first list empty case closer to where it is used --- compiler/gen/src/llvm/build.rs | 90 +++++++++++++++++----------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index ecba86ea0f..d385e37412 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -2118,51 +2118,6 @@ fn list_append<'a, 'ctx, 'env>( let second_list_len = load_list_len(builder, second_list_wrapper); - let if_first_list_is_empty = || { - match second_list_layout { - Layout::Builtin(Builtin::EmptyList) => empty_list(env), - Layout::Builtin(Builtin::List(elem_layout)) => { - // second_list_len > 0 - // We do this check to avoid allocating memory. If the second input - // list is empty, then we can just return the first list cloned - let second_list_length_comparison = - list_is_not_empty(builder, ctx, second_list_len); - - let build_second_list_then = || { - let elem_type = - basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); - let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); - - let (new_wrapper, _) = clone_nonempty_list( - env, - second_list_len, - load_list_ptr(builder, second_list_wrapper, ptr_type), - elem_layout, - ); - - BasicValueEnum::StructValue(new_wrapper) - }; - - let build_second_list_else = || empty_list(env); - - build_basic_phi2( - env, - parent, - second_list_length_comparison, - build_second_list_then, - build_second_list_else, - BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), - ) - } - _ => { - unreachable!( - "Invalid List layout for second input list of List.append: {:?}", - second_list_layout - ); - } - } - }; - match first_list_layout { Layout::Builtin(Builtin::EmptyList) => { match second_list_layout { @@ -2480,6 +2435,51 @@ fn list_append<'a, 'ctx, 'env>( } }; + let if_first_list_is_empty = || { + match second_list_layout { + Layout::Builtin(Builtin::EmptyList) => empty_list(env), + Layout::Builtin(Builtin::List(elem_layout)) => { + // second_list_len > 0 + // We do this check to avoid allocating memory. If the second input + // list is empty, then we can just return the first list cloned + let second_list_length_comparison = + list_is_not_empty(builder, ctx, second_list_len); + + let build_second_list_then = || { + let elem_type = + basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes); + let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic); + + let (new_wrapper, _) = clone_nonempty_list( + env, + second_list_len, + load_list_ptr(builder, second_list_wrapper, ptr_type), + elem_layout, + ); + + BasicValueEnum::StructValue(new_wrapper) + }; + + let build_second_list_else = || empty_list(env); + + build_basic_phi2( + env, + parent, + second_list_length_comparison, + build_second_list_then, + build_second_list_else, + BasicTypeEnum::StructType(collection(ctx, env.ptr_bytes)), + ) + } + _ => { + unreachable!( + "Invalid List layout for second input list of List.append: {:?}", + second_list_layout + ); + } + } + }; + build_basic_phi2( env, parent, From 3e61b6d1652c5454d6803456fcf3a74b9af70386 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 23:02:41 -0400 Subject: [PATCH 19/68] Fix list length calculation --- compiler/gen/src/llvm/build.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index d385e37412..cebb692983 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -2221,11 +2221,20 @@ fn list_append<'a, 'ctx, 'env>( "add_list_lengths", ); + let elem_bytes = env + .ptr_int() + .const_int(elem_layout.stack_size(env.ptr_bytes) as u64, false); + let combined_bytes = builder.build_int_mul( + combined_list_len, + elem_bytes, + "add_list_lengths", + ); + let combined_list_ptr = env .builder .build_array_malloc( elem_type, - combined_list_len, + combined_bytes, "create_combined_list_ptr", ) .unwrap(); From 1ecb795b965005dba4cda9cf5a12b99a3b024716 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 23:41:15 -0400 Subject: [PATCH 20/68] Revert "Fix list length calculation" This reverts commit 3e61b6d1652c5454d6803456fcf3a74b9af70386. --- compiler/gen/src/llvm/build.rs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index cebb692983..d385e37412 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -2221,20 +2221,11 @@ fn list_append<'a, 'ctx, 'env>( "add_list_lengths", ); - let elem_bytes = env - .ptr_int() - .const_int(elem_layout.stack_size(env.ptr_bytes) as u64, false); - let combined_bytes = builder.build_int_mul( - combined_list_len, - elem_bytes, - "add_list_lengths", - ); - let combined_list_ptr = env .builder .build_array_malloc( elem_type, - combined_bytes, + combined_list_len, "create_combined_list_ptr", ) .unwrap(); From 16b801a3f2ce1b60aa94eb4bdf69d46a77883bea Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 20 Jul 2020 21:39:43 -0400 Subject: [PATCH 21/68] Revise List docs some more --- compiler/builtins/docs/List.roc | 159 ++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 67 deletions(-) diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index b52e728f32..ae7652be0a 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -5,9 +5,7 @@ interface List ## Types ## A sequential list of values. -## # >>> [ 1, 2, 3 ] # a list of numbers -## -## >>> [ "a", "b", "c" ] # a list of strings +## # >>> [ 1, 2, 3 ] # a list of numbers # # >>> [ "a", "b", "c" ] # a list of strings ## ## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of floats ## @@ -203,17 +201,23 @@ map : List before, (before -> after) -> List after ## of the element to the conversion function. mapWithIndex : List before, (before, Int -> after) -> List after -## This works like #List.map, except the given function can return `Drop` to -## drop the transformed element - or wrap it in `Keep` to keep it. -## -mapOrDrop : List before, (before -> [ Keep after, Drop ]) -> List after - ## This works like #List.map, except at any time you can return `Err` to -## cancel the operation and return an error, or `Ok` to continue. +## cancel the entire operation immediately, and return that #Err. mapOrCancel : List before, (before -> Result after err) -> Result (List after) err -## If all the elements in the list are #Ok, -checkOk : List (Result ok err) -> Result (List ok) err +## This works like #List.map, except only the transformed values that are +## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped. +## +## >>> List.mapOks [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last +## +## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str) +## >>> +## >>> List.mapOks [ "", "a", "bc", "", "d", "ef", "" ] +mapOks : List before, (before -> Result after *) -> List after + +## If all the elements in the list are #Ok, return a new list containing the +## contents of those #Ok tags. If any elements are #Err, return #Err. +allOks : List (Result ok err) -> Result (List ok) err ## Add a single element to the end of a list. ## @@ -248,6 +252,8 @@ concat : List elem, List elem -> List elem ## >>> List.join [] join : List (List elem) -> List elem +## Like #List.map, except the transformation function wraps the return value +## in a list. At the end, all the lists get joined together into one list. joinMap : List before, (before -> List after) -> List after ## Like #List.join, but only keeps elements tagged with `Ok`. Elements @@ -263,7 +269,7 @@ joinOks : List (Result elem *) -> List elem ## Iterates over the shortest of the given lists and returns a list of `Pair` ## tags, each wrapping one of the elements in that list, along with the elements -## in the same position in # the other lists. +## in the same index in # the other lists. ## ## >>> List.zip [ "a1", "b1" "c1" ] [ "a2", "b2" ] [ "a3", "b3", "c3" ] ## @@ -271,10 +277,7 @@ joinOks : List (Result elem *) -> List elem ## ## > For a generalized version that returns whatever you like, instead of a `Pair`, ## > see `zipMap`. -zip : - List a, List b, -> List [ Pair a b ]* - List a, List b, List c, -> List [ Pair a b c ]* - List a, List b, List c, List d -> List [ Pair a b c d ]* +zip : List a, List b, -> List [ Pair a b ]* ## Like `zip` but you can specify what to do with each element. ## @@ -283,10 +286,7 @@ zip : ## >>> List.zipMap [ 1, 2, 3 ] [ 0, 5, 4 ] [ 2, 1 ] \num1 num2 num3 -> num1 + num2 - num3 ## ## Accepts up to 8 lists. -zipMap : - List a, List b, (a, b) -> List c | - List a, List b, List c, (a, b, c) -> List d | - List a, List b, List c, List d, (a, b, c, d) -> List e +zipMap : List a, List b, (a, b) -> List c ## Filter @@ -312,7 +312,7 @@ zipMap : ## If all elements in the list end up being kept, Roc will return the original ## list unaltered. ## -keepIf : List elem, (elem -> [True, False]) -> List elem +keepIf : List elem, (elem -> Bool) -> List elem ## Run the given function on each element of a list, and return all the ## elements for which the function returned `False`. @@ -323,30 +323,16 @@ keepIf : List elem, (elem -> [True, False]) -> List elem ## ## #List.dropIf has the same performance characteristics as #List.keepIf. ## See its documentation for details on those characteristics! -dropIf : List elem, (elem -> [True, False]) -> List elem - -## Takes the requested number of elements from the front of a list -## and returns them. -## -## >>> take 5 [ 1, 2, 3, 4, 5, 6, 7, 8 ] -## -## If there are fewer elements in the list than the requeted number, -## returns the entire list. -## -## >>> take 5 [ 1, 2 ] -take : List elem, Int -> List elem +dropIf : List elem, (elem -> Bool) -> List elem ## Access -## Returns the first element in the list, or `ListWasEmpty` if the list was empty. +## Returns the first element in the list, or `ListWasEmpty` if it was empty. first : List elem -> Result elem [ ListWasEmpty ]* -## Returns the last element in the list, or `ListWasEmpty` if the list was empty. +## Returns the last element in the list, or `ListWasEmpty` if it was empty. last : List elem -> Result elem [ ListWasEmpty ]* -## This takes a #Len because the maximum length of a #List is a #Len value, -## so #Len lets you specify any position up to the maximum length of -## the list. get : List elem, Len -> Result elem [ OutOfBounds ]* max : List (Num a) -> Result (Num a) [ ListWasEmpty ]* @@ -355,14 +341,17 @@ min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* ## Modify -## This takes a #Len because the maximum length of a #List is a #Len value, -## so #Len lets you specify any position up to the maximum length of -## the list. -set : List elem, Len, elem -> List elem - -## Add a new element to the end of a list. +## Replaces the element at the given index with a replacement. ## -## Returns a new list with the given element as its last element. +## >>> List.put [ "a", "b", "c" ] 1 "B" +## +## If the given index is outside the bounds of the list, returns the original +## list unmodified. +put : List elem, Len, elem -> List elem + +## Adds a new element to the end of the list. +## +## >>> List.append [ "a", "b" ] "c" ## ## ## Performance Details ## @@ -372,9 +361,9 @@ set : List elem, Len, elem -> List elem ## module's documentation. append : List elem, elem -> List elem -## Add a new element to the beginning of a list. +## Adds a new element to the beginning of the list. ## -## Returns a new list with the given element as its first element. +## >>> List.prepend [ "b", "c" ] "a" ## ## ## Performance Details ## @@ -428,7 +417,7 @@ dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpt ## runs *much* faster. This is because for #List.dropLast, removing the last element ## in-place is as easy as reducing the length of the list by 1. In contrast, ## removing the first element from the list involves copying every other element -## in the list into the position before it - which is massively more costly. +## in the list into the index before it - which is massively more costly. ## ## In the case of a Shared list, ## @@ -438,41 +427,60 @@ dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpt ## dropLast | #List.last + clone rest of list | #List.last + clone rest of list | dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmpty ]* -## Drops the given number of elements from the end of the list. +## Returns the given number of elements from the beginning of the list. ## -## Returns a new list without the dropped elements. +## >>> List.takeFirst 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ] ## -## To remove elements from a list while also returning a list of the removed -## elements, use #List.split. +## If there are fewer elements in the list than the requested number, +## returns the entire list. ## -## To remove elements from the beginning of the list, use #List.dropFromFront. +## >>> List.takeFirst 5 [ 1, 2 ] +## +## To *remove* elements from the beginning of the list, use #List.takeLast. +## +## To remove elements from both the beginning and end of the list, +## use #List.sublist. +## +## To split the list into two lists, use #List.split. ## ## ## Performance Details ## -## When given a Unique list, this runs extremely fast. It subtracts the given -## number from the list's length (down to a minimum of 0) in-place, and that's it. +## When given a Unique list, this runs extremely fast. It sets the list's length +## to the given length value, and frees the leftover elements. This runs very +## slightly faster than #List.takeLast. ## -## In fact, `List.drop 1 list` runs faster than `List.dropLast list` when given -## a Unique list, because #List.dropLast returns the element it dropped - +## In fact, `List.takeFirst 1 list` runs faster than `List.first list` when given +## a Unique list, because #List.first returns the first element as well - ## which introduces a conditional bounds check as well as a memory load. -drop : List elem, Len -> List elem +takeFirst : List elem, Len -> List elem -## Drops the given number of elements from the front of the list. +## Returns the given number of elements from the end of the list. ## -## Returns a new list without the dropped elements. +## >>> List.takeLast 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ] ## -## To remove elements from a list while also returning a list of the removed -## elements, use #List.split. +## If there are fewer elements in the list than the requested number, +## returns the entire list. +## +## >>> List.takeLast 5 [ 1, 2 ] +## +## To *remove* elements from the end of the list, use #List.takeFirst. +## +## To remove elements from both the beginning and end of the list, +## use #List.sublist. +## +## To split the list into two lists, use #List.split. ## ## ## Performance Details ## -## When given a Unique list, this runs extremely fast. It subtracts the given -## number from the list's length (down to a minimum of 0) in-place, and that's it. +## When given a Unique list, this runs extremely fast. It moves the list's +## pointer to the index at the given length value, updates its length, +## and frees the leftover elements. This runs very nearly as fast as +## #List.takeFirst on a Unique list. ## -## In fact, `List.drop 1 list` runs faster than `List.dropLast list` when given -## a Unique list, because #List.dropLast returns the element it dropped - +## In fact, `List.takeLast 1 list` runs faster than `List.first list` when given +## a Unique list, because #List.first returns the first element as well - ## which introduces a conditional bounds check as well as a memory load. -dropFromFront : List elem, Len -> List elem +takeLast : List elem, Len -> List elem ## Deconstruct @@ -485,6 +493,23 @@ dropFromFront : List elem, Len -> List elem ## `others` list will have the same elements as the original list.) split : List elem, Len -> { before: List elem, others: List elem } +## Returns a subsection of the given list, beginning at the `start` index and +## including a total of `len` elements. +## +## If `start` is outside the bounds of the given list, returns the empty list. +## +## >>> List.sublist { start: 4, len: 0 } [ 1, 2, 3 ] +## +## If more elements are requested than exist in the list, returns as many as it can. +## +## >>> List.sublist { start: 2, len: 10 } [ 1, 2, 3, 4, 5 ] +## +## > If you want a sublist which goes all the way to the end of the list, no +## > matter how long the list is, #List.takeLast can do that more efficiently. +## +## Some languages have a function called **`slice`** which works similarly to this. +sublist : List elem, { start : Len, len : Len } -> List elem + ## Build a value using each element in the list. ## ## Starting with a given `state` value, this walks through each element in the From 5b7427b76209667291083ef25b66ccf65c034778 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 21 Jul 2020 01:17:24 -0400 Subject: [PATCH 22/68] Drop unused argument --- editor/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 2868a32112..83597100cf 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -151,7 +151,6 @@ fn run_event_loop() -> Result<(), Box> { } => { if let Some(virtual_keycode) = input.virtual_keycode { handle_keydown( - &mut text_state, input.state, virtual_keycode, keyboard_modifiers, @@ -226,7 +225,6 @@ fn run_event_loop() -> Result<(), Box> { } fn handle_keydown( - text_state: &mut String, elem_state: ElementState, virtual_keycode: VirtualKeyCode, _modifiers: ModifiersState, From 94367867ea187877fde84b42d842c80fcc127f68 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 21 Jul 2020 01:34:30 -0400 Subject: [PATCH 23/68] cargo fmt --- editor/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 83597100cf..a26b8fbafa 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -150,11 +150,7 @@ fn run_event_loop() -> Result<(), Box> { .. } => { if let Some(virtual_keycode) = input.virtual_keycode { - handle_keydown( - input.state, - virtual_keycode, - keyboard_modifiers, - ); + handle_keydown(input.state, virtual_keycode, keyboard_modifiers); } } winit::event::Event::WindowEvent { From 799e137f554f2d09b860a72f269c483a397dc037 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 21 Jul 2020 01:35:10 -0400 Subject: [PATCH 24/68] Restore CLI --- Cargo.lock | 145 ++++++++++++- Cargo.toml | 3 +- cli/src/main.rs | 398 +--------------------------------- cli/src/repl.rs | 44 ++-- compiler/build/src/program.rs | 2 +- 5 files changed, 181 insertions(+), 411 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e11fcd579f..2dd5ed532a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -207,6 +207,35 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "clap" +version = "3.0.0-beta.1" +source = "git+https://github.com/rtfeldman/clap#e1d83a78804a271b053d4d21f69b67f7eb01ad01" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "strsim", + "termcolor", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap_derive" +version = "3.0.0-beta.1" +source = "git+https://github.com/rtfeldman/clap#e1d83a78804a271b053d4d21f69b67f7eb01ad01" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -329,7 +358,7 @@ checksum = "1fc755679c12bda8e5523a71e4d654b6bf2e14bd838dfc48cde6559a05caf7d1" dependencies = [ "atty", "cast", - "clap", + "clap 2.33.0", "criterion-plot", "csv", "itertools", @@ -856,6 +885,24 @@ dependencies = [ "xi-unicode", ] +[[package]] +name = "hashbrown" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34f595585f103464d8d2f6e9864682d74c1601fed5e07d62b1c9058dba8246fb" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.10" @@ -911,6 +958,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "indexmap" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b88cd59ee5f71fea89a62248fc8f387d44400cefe05ef548466d61ced9029a7" +dependencies = [ + "autocfg 1.0.0", + "hashbrown", +] + [[package]] name = "indoc" version = "0.3.5" @@ -1434,6 +1491,32 @@ dependencies = [ "difference", ] +[[package]] +name = "proc-macro-error" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" +dependencies = [ + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", + "syn-mid", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.15" @@ -1757,6 +1840,43 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "roc-cli" +version = "0.1.0" +dependencies = [ + "bumpalo", + "clap 3.0.0-beta.1", + "im", + "im-rc", + "indoc", + "inkwell", + "inlinable_string", + "maplit", + "pretty_assertions", + "quickcheck", + "quickcheck_macros", + "roc_build", + "roc_builtins", + "roc_can", + "roc_collections", + "roc_constrain", + "roc_editor", + "roc_gen", + "roc_load", + "roc_module", + "roc_mono", + "roc_parse", + "roc_problem", + "roc_region", + "roc_reporting", + "roc_solve", + "roc_types", + "roc_unify", + "roc_uniq", + "target-lexicon", + "tokio", +] + [[package]] name = "roc_build" version = "0.1.0" @@ -2361,6 +2481,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + [[package]] name = "syn" version = "0.15.44" @@ -2383,6 +2509,17 @@ dependencies = [ "unicode-xid 0.2.0", ] +[[package]] +name = "syn-mid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" +dependencies = [ + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", +] + [[package]] name = "synstructure" version = "0.12.4" @@ -2499,6 +2636,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + [[package]] name = "unicode-width" version = "0.1.7" diff --git a/Cargo.toml b/Cargo.toml index 8de62c5b0a..1bf23787c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,8 @@ members = [ "vendor/ena", "vendor/pathfinding", "vendor/pretty", - "editor" + "editor", + "cli" ] # Optimizations based on https://deterministic.space/high-performance-rust.html diff --git a/cli/src/main.rs b/cli/src/main.rs index 5932ce29f6..4d3bead09e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -2,33 +2,17 @@ extern crate clap; use bumpalo::Bump; -use inkwell::context::Context; -use inkwell::module::Linkage; -use inkwell::passes::PassManager; -use inkwell::types::BasicType; -use inkwell::OptimizationLevel; -use roc_collections::all::ImMap; use roc_collections::all::MutMap; -use roc_gen::layout_id::LayoutIds; -use roc_gen::llvm::build::{ - build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel, -}; -use roc_gen::llvm::convert::basic_type_from_layout; -use roc_build::program::build; -use roc_load::file::{LoadedModule, LoadingProblem}; -use roc_module::symbol::Symbol; -use roc_mono::expr::{Env, Expr, PartialProc, Procs}; -use roc_mono::layout::Layout; +use roc_gen::llvm::build::OptLevel; +use roc_build::program::gen; +use roc_load::file::LoadingProblem; use std::time::SystemTime; use clap::{App, Arg, ArgMatches}; -use inkwell::targets::{ - CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple, -}; use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; use std::process; -use target_lexicon::{Architecture, Environment, OperatingSystem, Triple, Vendor}; +use target_lexicon::Triple; use tokio::process::Command; use tokio::runtime::Builder; @@ -247,377 +231,3 @@ async fn build_file( Ok(binary_path) } - - -// TODO this should probably use more helper functions -#[allow(clippy::cognitive_complexity)] -fn gen( - arena: &Bump, - loaded: LoadedModule, - filename: PathBuf, - target: Triple, - dest_filename: &Path, - opt_level: OptLevel, -) { - use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE}; - - let src = loaded.src; - let home = loaded.module_id; - let src_lines: Vec<&str> = src.split('\n').collect(); - let palette = DEFAULT_PALETTE; - - // Report parsing and canonicalization problems - let alloc = RocDocAllocator::new(&src_lines, home, &loaded.interns); - - for problem in loaded.can_problems.into_iter() { - let report = can_problem(&alloc, filename.clone(), problem); - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - println!("\n{}\n", buf); - } - - for problem in loaded.type_problems.into_iter() { - let report = type_problem(&alloc, filename.clone(), problem); - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - println!("\n{}\n", buf); - } - - // Look up the types and expressions of the `provided` values - - // TODO instead of hardcoding this to `main`, use the `provided` list and gen all of them. - let ident_ids = loaded.interns.all_ident_ids.get(&home).unwrap(); - let main_ident_id = *ident_ids.get_id(&"main".into()).unwrap_or_else(|| { - todo!("TODO gracefully handle the case where `main` wasn't declared in the app") - }); - let main_symbol = Symbol::new(home, main_ident_id); - let mut main_var = None; - let mut main_expr = None; - - for (symbol, var) in loaded.exposed_vars_by_symbol { - if symbol == main_symbol { - main_var = Some(var); - - break; - } - } - - let mut decls_by_id = loaded.declarations_by_id; - let home_decls = decls_by_id - .remove(&loaded.module_id) - .expect("Root module ID not found in loaded declarations_by_id"); - - // We use a loop label here so we can break all the way out of a nested - // loop inside DeclareRec if we find the expr there. - // - // https://doc.rust-lang.org/1.30.0/book/first-edition/loops.html#loop-labels - 'find_expr: for decl in home_decls.iter() { - use roc_can::def::Declaration::*; - - match decl { - Declare(def) => { - if def.pattern_vars.contains_key(&main_symbol) { - main_expr = Some(def.loc_expr.clone()); - - break 'find_expr; - } - } - - DeclareRec(defs) => { - for def in defs { - if def.pattern_vars.contains_key(&main_symbol) { - main_expr = Some(def.loc_expr.clone()); - - break 'find_expr; - } - } - } - InvalidCycle(_, _) => {} - } - } - - let loc_expr = main_expr.unwrap_or_else(|| { - panic!("TODO gracefully handle the case where `main` was declared but not exposed") - }); - let mut subs = loaded.solved.into_inner(); - let content = match main_var { - Some(var) => subs.get_without_compacting(var).content, - None => todo!("TODO gracefully handle the case where `main` was declared but not exposed"), - }; - - // Generate the binary - - let context = Context::create(); - let module = module_from_builtins(&context, "app"); - let builder = context.create_builder(); - let fpm = PassManager::create(&module); - - roc_gen::llvm::build::add_passes(&fpm, opt_level); - - fpm.initialize(); - - // Compute main_fn_type before moving subs to Env - let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; - let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| { - panic!( - "Code gen error in CLI: could not convert to layout. Err was {:?}", - err - ) - }); - - let main_fn_type = - basic_type_from_layout(&arena, &context, &layout, ptr_bytes).fn_type(&[], false); - let main_fn_name = "$main"; - - // Compile and add all the Procs before adding main - let mut env = roc_gen::llvm::build::Env { - arena: &arena, - builder: &builder, - context: &context, - interns: loaded.interns, - module: arena.alloc(module), - ptr_bytes, - }; - let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); - let mut layout_ids = LayoutIds::default(); - let mut procs = Procs::default(); - let mut mono_problems = std::vec::Vec::new(); - let mut mono_env = Env { - arena, - subs: &mut subs, - problems: &mut mono_problems, - home, - ident_ids: &mut ident_ids, - pointer_size: ptr_bytes, - jump_counter: arena.alloc(0), - }; - - // Add modules' decls to Procs - for (_, mut decls) in decls_by_id - .drain() - .chain(std::iter::once((loaded.module_id, home_decls))) - { - for decl in decls.drain(..) { - use roc_can::def::Declaration::*; - use roc_can::expr::Expr::*; - use roc_can::pattern::Pattern::*; - - match decl { - Declare(def) => match def.loc_pattern.value { - Identifier(symbol) => { - match def.loc_expr.value { - Closure(annotation, _, _, loc_args, boxed_body) => { - let (loc_body, ret_var) = *boxed_body; - - procs.insert_named( - &mut mono_env, - symbol, - annotation, - loc_args, - loc_body, - ret_var, - ); - } - body => { - let proc = PartialProc { - annotation: def.expr_var, - // This is a 0-arity thunk, so it has no arguments. - patterns: bumpalo::collections::Vec::new_in(arena), - body, - }; - - procs.user_defined.insert(symbol, proc); - procs.module_thunks.insert(symbol); - } - }; - } - other => { - todo!("TODO gracefully handle Declare({:?})", other); - } - }, - DeclareRec(_defs) => { - todo!("TODO support DeclareRec"); - } - InvalidCycle(_loc_idents, _regions) => { - todo!("TODO handle InvalidCycle"); - } - } - } - } - - // Populate Procs further and get the low-level Expr from the canonical Expr - let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs); - - // Put this module's ident_ids back in the interns, so we can use them in env. - env.interns.all_ident_ids.insert(home, ident_ids); - - let mut headers = Vec::with_capacity(procs.len()); - let (mut proc_map, runtime_errors) = procs.into_map(); - - assert_eq!(runtime_errors, roc_collections::all::MutSet::default()); - - // 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, mut procs_by_layout) in proc_map.drain() { - for (layout, proc) in procs_by_layout.drain() { - let (fn_val, arg_basic_types) = - build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); - - headers.push((proc, fn_val, arg_basic_types)); - } - } - - // Build each proc using its header info. - for (proc, fn_val, arg_basic_types) in headers { - // NOTE: This is here to be uncommented in case verification fails. - // (This approach means we don't have to defensively clone name here.) - // - // println!("\n\nBuilding and then verifying function {}\n\n", name); - build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types); - - if fn_val.verify(true) { - fpm.run_on(&fn_val); - } else { - // NOTE: If this fails, uncomment the above println to debug. - panic!( - "Non-main function failed LLVM verification. Uncomment the above println to debug!" - ); - } - } - - // Add main to the module. - let cc = get_call_conventions(target.default_calling_convention().unwrap()); - let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); - - main_fn.set_call_conventions(cc); - main_fn.set_linkage(Linkage::External); - - // Add main's body - let basic_block = context.append_basic_block(main_fn, "entry"); - - builder.position_at_end(basic_block); - - let ret = roc_gen::llvm::build::build_expr( - &env, - &mut layout_ids, - &ImMap::default(), - main_fn, - &main_body, - ); - - builder.build_return(Some(&ret)); - - // 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!("Function {} failed LLVM verification.", main_fn_name); - } - - // Verify the module - if let Err(errors) = env.module.verify() { - panic!("đŸ˜± LLVM errors when defining module: {:?}", errors); - } - - // Uncomment this to see the module's optimized LLVM instruction output: - // env.module.print_to_stderr(); - - // Emit the .o file - - // NOTE: arch_str is *not* the same as the beginning of the magic target triple - // string! For example, if it's "x86-64" here, the magic target triple string - // will begin with "x86_64" (with an underscore) instead. - let arch_str = match target.architecture { - Architecture::X86_64 => { - Target::initialize_x86(&InitializationConfig::default()); - - "x86-64" - } - Architecture::Arm(_) if cfg!(feature = "target-arm") => { - // NOTE: why not enable arm and wasm by default? - // - // We had some trouble getting them to link properly. This may be resolved in the - // future, or maybe it was just some weird configuration on one machine. - Target::initialize_arm(&InitializationConfig::default()); - - "arm" - } - Architecture::Wasm32 if cfg!(feature = "target-webassembly") => { - Target::initialize_webassembly(&InitializationConfig::default()); - - "wasm32" - } - _ => panic!( - "TODO gracefully handle unsupported target architecture: {:?}", - target.architecture - ), - }; - - let opt = OptimizationLevel::Default; - let reloc = RelocMode::Default; - let model = CodeModel::Default; - - // Best guide I've found on how to determine these magic strings: - // - // https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures - // - // Clang docs are particularly helpful: http://clang.llvm.org/docs/CrossCompilation.html - let target_triple_str = match target { - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Unknown, - operating_system: OperatingSystem::Linux, - .. - } => "x86_64-unknown-linux-gnu", - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Pc, - operating_system: OperatingSystem::Linux, - .. - } => "x86_64-pc-linux-gnu", - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Unknown, - operating_system: OperatingSystem::Darwin, - .. - } => "x86_64-unknown-darwin10", - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Apple, - operating_system: OperatingSystem::Darwin, - .. - } => "x86_64-apple-darwin10", - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Pc, - operating_system: OperatingSystem::Windows, - environment: Environment::Msvc, - .. - } => "x86_64-pc-win32-gnu", - _ => panic!("TODO gracefully handle unsupported target: {:?}", target), - }; - let target_machine = Target::from_name(arch_str) - .unwrap() - .create_target_machine( - &TargetTriple::create(target_triple_str), - arch_str, - "+avx2", // TODO this string was used uncritically from an example, and should be reexamined - opt, - reloc, - model, - ) - .unwrap(); - - target_machine - .write_to_file(&env.module, FileType::Object, &dest_filename) - .expect("Writing .o file failed"); - - println!("\nSuccess!\n\n\t—> {}\n", dest_filename.display()); -} diff --git a/cli/src/repl.rs b/cli/src/repl.rs index b65f7724ac..6ac71e2c3f 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -20,7 +20,7 @@ use roc_gen::llvm::convert::basic_type_from_layout; use roc_module::ident::Ident; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; use roc_mono::expr::Procs; -use roc_mono::layout::Layout; +use roc_mono::layout::{Layout, LayoutCache}; use roc_parse::ast::{self, Attempting}; use roc_parse::blankspace::space0_before; use roc_parse::parser::{loc, Fail, FailReason, Parser, State}; @@ -257,29 +257,45 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St pointer_size: ptr_bytes, jump_counter: arena.alloc(0), }; + let main_body = roc_mono::expr::Expr::new(&mut mono_env, loc_expr.value, &mut procs); + let mut headers = { + let num_headers = match &procs.pending_specializations { + Some(map) => map.len(), + None => 0, + }; - // Put this module's ident_ids back in the interns, so we can use them in Env. - env.interns.all_ident_ids.insert(home, ident_ids); - - let mut headers = Vec::with_capacity(procs.len()); - let (mut proc_map, runtime_errors) = procs.into_map(); + Vec::with_capacity(num_headers) + }; + let mut layout_cache = LayoutCache::default(); + let mut procs = roc_mono::expr::specialize_all(&mut mono_env, procs, &mut layout_cache); assert_eq!( - runtime_errors, - roc_collections::all::MutSet::default(), - "TODO code gen runtime exception functions" + procs.runtime_errors, + roc_collections::all::MutMap::default() ); + // 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, mut procs_by_layout) in proc_map.drain() { - for (layout, proc) in procs_by_layout.drain() { - let (fn_val, arg_basic_types) = - build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); + for ((symbol, layout), proc) in procs.specialized.drain() { + use roc_mono::expr::InProgressProc::*; - headers.push((proc, fn_val, arg_basic_types)); + match proc { + InProgress => { + panic!("A specialization was still marked InProgress after monomorphization had completed: {:?} with layout {:?}", symbol, layout); + } + Done(proc) => { + let (fn_val, arg_basic_types) = + build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); + + headers.push((proc, fn_val, arg_basic_types)); + } } } diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index d41b89e4d6..3c66e53113 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -25,7 +25,7 @@ use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor}; // TODO this should probably use more helper functions // TODO make this polymorphic in the llvm functions so it can be reused for another backend. #[allow(clippy::cognitive_complexity)] -pub fn build( +pub fn gen( arena: &Bump, loaded: LoadedModule, filename: PathBuf, From a0caaaaa406b00fb49bf78e3f7e672a04ddca626 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 21 Jul 2020 02:22:50 -0400 Subject: [PATCH 25/68] cargo fmt --- cli/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 4d3bead09e..246c92ffbe 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -2,9 +2,9 @@ extern crate clap; use bumpalo::Bump; +use roc_build::program::gen; use roc_collections::all::MutMap; use roc_gen::llvm::build::OptLevel; -use roc_build::program::gen; use roc_load::file::LoadingProblem; use std::time::SystemTime; From c9883be8a2abdd4721d794ecad477ce0f0a33381 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 11:21:32 -0400 Subject: [PATCH 26/68] Introduce optional record fields --- compiler/can/src/annotation.rs | 28 +++++- compiler/can/src/operator.rs | 12 ++- compiler/parse/src/ast.rs | 26 ++++-- compiler/parse/src/expr.rs | 48 ++++++++-- compiler/parse/src/parser.rs | 13 ++- compiler/types/src/pretty_print.rs | 33 +++++-- compiler/types/src/solved_types.rs | 25 +++-- compiler/types/src/subs.rs | 42 ++++++--- compiler/types/src/types.rs | 142 ++++++++++++++++++++++++++--- 9 files changed, 309 insertions(+), 60 deletions(-) diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index eb9ae7c941..bc64df7f7b 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -6,7 +6,7 @@ use roc_module::symbol::Symbol; use roc_parse::ast::{AssignedField, Tag, TypeAnnotation}; use roc_region::all::{Located, Region}; use roc_types::subs::{VarStore, Variable}; -use roc_types::types::{Alias, Problem, Type}; +use roc_types::types::{Alias, Problem, RecordField, Type}; #[derive(Clone, Debug, PartialEq)] pub struct Annotation { @@ -380,8 +380,9 @@ fn can_assigned_fields<'a>( introduced_variables: &mut IntroducedVariables, local_aliases: &mut SendMap, references: &mut MutSet, -) -> SendMap { +) -> SendMap> { use roc_parse::ast::AssignedField::*; + use roc_types::types::RecordField::*; // SendMap doesn't have a `with_capacity` let mut field_types = SendMap::default(); @@ -398,7 +399,7 @@ fn can_assigned_fields<'a>( // a duplicate let new_name = 'inner: loop { match field { - LabeledValue(field_name, _, annotation) => { + RequiredValue(field_name, _, annotation) => { let field_type = can_annotation_help( env, &annotation.value, @@ -411,7 +412,24 @@ fn can_assigned_fields<'a>( ); let label = Lowercase::from(field_name.value); - field_types.insert(label.clone(), field_type); + field_types.insert(label.clone(), Required(field_type)); + + break 'inner label; + } + OptionalValue(field_name, _, annotation) => { + let field_type = can_annotation_help( + env, + &annotation.value, + annotation.region, + scope, + var_store, + introduced_variables, + local_aliases, + references, + ); + + let label = Lowercase::from(field_name.value); + field_types.insert(label.clone(), Optional(field_type)); break 'inner label; } @@ -428,7 +446,7 @@ fn can_assigned_fields<'a>( } }; - field_types.insert(field_name.clone(), field_type); + field_types.insert(field_name.clone(), Required(field_type)); break 'inner field_name; } diff --git a/compiler/can/src/operator.rs b/compiler/can/src/operator.rs index 3786cb12f2..8b7e13d70e 100644 --- a/compiler/can/src/operator.rs +++ b/compiler/can/src/operator.rs @@ -266,7 +266,15 @@ fn desugar_field<'a>( use roc_parse::ast::AssignedField::*; match field { - LabeledValue(loc_str, spaces, loc_expr) => AssignedField::LabeledValue( + RequiredValue(loc_str, spaces, loc_expr) => RequiredValue( + Located { + value: loc_str.value, + region: loc_str.region, + }, + spaces, + desugar_expr(arena, loc_expr), + ), + OptionalValue(loc_str, spaces, loc_expr) => OptionalValue( Located { value: loc_str.value, region: loc_str.region, @@ -284,7 +292,7 @@ fn desugar_field<'a>( region: loc_str.region, }; - AssignedField::LabeledValue( + RequiredValue( Located { value: loc_str.value, region: loc_str.region, diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index d0fff3ce4b..1fe3242a85 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -276,8 +276,14 @@ pub enum Tag<'a> { #[derive(Debug, Clone, PartialEq)] pub enum AssignedField<'a, Val> { - // Both a label and a value, e.g. `{ name: "blah" }` - LabeledValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc), + // A required field with a label, e.g. `{ name: "blah" }` or `{ name : Str }` + RequiredValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc), + + // An optional field with a label, e.g. `{ name ? "blah" }` + // + // NOTE: This only comes up in type annotations (e.g. `name ? Str`) + // and in destructuring patterns (e.g. `{ name ? "blah" }`) + OptionalValue(Loc<&'a str>, &'a [CommentOrNewline<'a>], &'a Loc), // A label with no value, e.g. `{ name }` (this is sugar for { name: name }) LabelOnly(Loc<&'a str>), @@ -309,9 +315,14 @@ pub enum Pattern<'a> { /// around the destructured names, e.g. { x ### x does stuff ###, y } /// In practice, these patterns will always be Identifier RecordDestructure(&'a [Loc>]), - /// A field pattern, e.g. { x: Just 0 } -> ... - /// can only occur inside of a RecordDestructure - RecordField(&'a str, &'a Loc>), + + /// A required field pattern, e.g. { x: Just 0 } -> ... + /// Can only occur inside of a RecordDestructure + RequiredField(&'a str, &'a Loc>), + + /// An optional field pattern, e.g. { x ? Just 0 } -> ... + /// Can only occur inside of a RecordDestructure + OptionalField(&'a str, &'a Loc>), /// This is used only to avoid cloning when reordering expressions (e.g. in desugar()). /// It lets us take an (&Expr) and create a plain (Expr) from it. @@ -414,7 +425,10 @@ impl<'a> Pattern<'a> { .iter() .zip(fields_y.iter()) .all(|(p, q)| p.value.equivalent(&q.value)), - (RecordField(x, inner_x), RecordField(y, inner_y)) => { + (RequiredField(x, inner_x), RequiredField(y, inner_y)) => { + x == y && inner_x.value.equivalent(&inner_y.value) + } + (OptionalField(x, inner_x), OptionalField(y, inner_y)) => { x == y && inner_x.value.equivalent(&inner_y.value) } (Nested(x), Nested(y)) => x.equivalent(y), diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index adf3efa27e..5e7930c698 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -332,17 +332,32 @@ pub fn assigned_expr_field_to_pattern<'a>( ) -> Result, Fail> { // the assigned fields always store spaces, but this slice is often empty Ok(match assigned_field { - AssignedField::LabeledValue(name, spaces, value) => { + AssignedField::RequiredValue(name, spaces, value) => { let pattern = expr_to_pattern(arena, &value.value)?; let result = arena.alloc(Located { region: value.region, value: pattern, }); if spaces.is_empty() { - Pattern::RecordField(name.value, result) + Pattern::RequiredField(name.value, result) } else { Pattern::SpaceAfter( - arena.alloc(Pattern::RecordField(name.value, result)), + arena.alloc(Pattern::RequiredField(name.value, result)), + spaces, + ) + } + } + AssignedField::OptionalValue(name, spaces, value) => { + let pattern = expr_to_pattern(arena, &value.value)?; + let result = arena.alloc(Located { + region: value.region, + value: pattern, + }); + if spaces.is_empty() { + Pattern::OptionalField(name.value, result) + } else { + Pattern::SpaceAfter( + arena.alloc(Pattern::OptionalField(name.value, result)), spaces, ) } @@ -368,7 +383,7 @@ pub fn assigned_pattern_field_to_pattern<'a>( ) -> Result>, Fail> { // the assigned fields always store spaces, but this slice is often empty Ok(match assigned_field { - AssignedField::LabeledValue(name, spaces, value) => { + AssignedField::RequiredValue(name, spaces, value) => { let pattern = value.value.clone(); let region = Region::span_across(&value.region, &value.region); let result = arena.alloc(Located { @@ -376,12 +391,31 @@ pub fn assigned_pattern_field_to_pattern<'a>( value: pattern, }); if spaces.is_empty() { - Located::at(region, Pattern::RecordField(name.value, result)) + Located::at(region, Pattern::RequiredField(name.value, result)) } else { Located::at( region, Pattern::SpaceAfter( - arena.alloc(Pattern::RecordField(name.value, result)), + arena.alloc(Pattern::RequiredField(name.value, result)), + spaces, + ), + ) + } + } + AssignedField::OptionalValue(name, spaces, value) => { + let pattern = value.value.clone(); + let region = Region::span_across(&value.region, &value.region); + let result = arena.alloc(Located { + region: value.region, + value: pattern, + }); + if spaces.is_empty() { + Located::at(region, Pattern::OptionalField(name.value, result)) + } else { + Located::at( + region, + Pattern::SpaceAfter( + arena.alloc(Pattern::OptionalField(name.value, result)), spaces, ), ) @@ -580,7 +614,7 @@ fn annotation_or_alias<'a>( loc_ann, ) } - RecordField(_, _) => { + RequiredField(_, _) | OptionalField(_, _) => { unreachable!("This should only be possible inside a record destruture."); } } diff --git a/compiler/parse/src/parser.rs b/compiler/parse/src/parser.rs index 5f4364728d..35511e557c 100644 --- a/compiler/parse/src/parser.rs +++ b/compiler/parse/src/parser.rs @@ -912,21 +912,26 @@ macro_rules! record_field { use $crate::ast::AssignedField::*; use $crate::blankspace::{space0, space0_before}; use $crate::ident::lowercase_ident; + use $crate::parser::Either::*; // You must have a field name, e.g. "email" let (loc_label, state) = loc!(lowercase_ident()).parse(arena, state)?; let (spaces, state) = space0($min_indent).parse(arena, state)?; + // Having a value is optional; both `{ email }` and `{ email: blah }` work. // (This is true in both literals and types.) - let (opt_loc_val, state) = $crate::parser::optional(skip_first!( - char(':'), - space0_before($val_parser, $min_indent) + let (opt_loc_val, state) = $crate::parser::optional(either!( + skip_first!(char(':'), space0_before($val_parser, $min_indent)), + skip_first!(char('?'), space0_before($val_parser, $min_indent)) )) .parse(arena, state)?; let answer = match opt_loc_val { - Some(loc_val) => LabeledValue(loc_label, spaces, arena.alloc(loc_val)), + Some(either) => match either { + First(loc_val) => RequiredValue(loc_label, spaces, arena.alloc(loc_val)), + Second(loc_val) => OptionalValue(loc_label, spaces, arena.alloc(loc_val)), + }, // If no value was provided, record it as a Var. // Canonicalize will know what to do with a Var later. None => { diff --git a/compiler/types/src/pretty_print.rs b/compiler/types/src/pretty_print.rs index 1e85280f94..6a6f1b321a 100644 --- a/compiler/types/src/pretty_print.rs +++ b/compiler/types/src/pretty_print.rs @@ -1,6 +1,6 @@ use crate::boolean_algebra::Bool; use crate::subs::{Content, FlatType, Subs, Variable}; -use crate::types::name_type_var; +use crate::types::{name_type_var, RecordField}; use roc_collections::all::{MutMap, MutSet}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::{Interns, ModuleId, Symbol}; @@ -159,10 +159,17 @@ fn find_names_needed( } Structure(Record(fields, ext_var)) => { let mut sorted_fields: Vec<_> = fields.iter().collect(); - sorted_fields.sort(); - for (_, var) in sorted_fields { - find_names_needed(*var, subs, roots, root_appearances, names_taken); + sorted_fields.sort_by(|(label1, _), (label2, _)| label1.cmp(label2)); + + for (_, field) in sorted_fields { + find_names_needed( + field.into_inner(), + subs, + roots, + root_appearances, + names_taken, + ); } find_names_needed(ext_var, subs, roots, root_appearances, names_taken); @@ -391,6 +398,8 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String let mut any_written_yet = false; for (label, field_var) in sorted_fields { + use RecordField::*; + if any_written_yet { buf.push_str(", "); } else { @@ -398,10 +407,20 @@ fn write_flat_type(env: &Env, flat_type: FlatType, subs: &Subs, buf: &mut String } buf.push_str(label.as_str()); - buf.push_str(" : "); + let var = match field_var { + Optional(var) => { + buf.push_str(" ? "); + var + } + Required(var) => { + buf.push_str(" : "); + var + } + }; + write_content( env, - subs.get_without_compacting(field_var).content, + subs.get_without_compacting(var).content, subs, buf, parens, @@ -583,7 +602,7 @@ pub fn chase_ext_tag_union( pub fn chase_ext_record( subs: &Subs, var: Variable, - fields: &mut MutMap, + fields: &mut MutMap>, ) -> Result<(), (Variable, Content)> { use crate::subs::Content::*; use crate::subs::FlatType::*; diff --git a/compiler/types/src/solved_types.rs b/compiler/types/src/solved_types.rs index d756e7d661..2b621805d5 100644 --- a/compiler/types/src/solved_types.rs +++ b/compiler/types/src/solved_types.rs @@ -1,6 +1,6 @@ use crate::boolean_algebra; use crate::subs::{FlatType, Subs, VarId, Variable}; -use crate::types::{Problem, Type}; +use crate::types::{Problem, RecordField, Type}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; @@ -33,7 +33,7 @@ pub enum SolvedType { Wildcard, /// Inline type alias, e.g. `as List a` in `[ Cons a (List a), Nil ] as List a` Record { - fields: Vec<(Lowercase, SolvedType)>, + fields: Vec<(Lowercase, RecordField)>, /// The row type variable in an open record, e.g. the `r` in `{ name: Str }r`. /// This is None if it's a closed record annotation like `{ name: Str }`. ext: Box, @@ -122,8 +122,14 @@ impl SolvedType { Record(fields, box_ext) => { let solved_ext = Self::from_type(solved_subs, *box_ext); let mut solved_fields = Vec::with_capacity(fields.len()); - for (label, typ) in fields { - let solved_type = Self::from_type(solved_subs, typ); + + for (label, field) in fields { + use crate::types::RecordField::*; + + let solved_type = match field { + Optional(typ) => RecordField::Optional(Self::from_type(solved_subs, typ)), + Required(typ) => RecordField::Required(Self::from_type(solved_subs, typ)), + }; solved_fields.push((label.clone(), solved_type)); } @@ -234,10 +240,15 @@ impl SolvedType { Record(fields, ext_var) => { let mut new_fields = Vec::with_capacity(fields.len()); - for (label, var) in fields { - let field = Self::from_var(subs, var); + for (label, field) in fields { + use RecordField::*; - new_fields.push((label, field)); + let solved_type = match field { + Optional(var) => Optional(Self::from_var(subs, var)), + Required(var) => Required(Self::from_var(subs, var)), + }; + + new_fields.push((label, solved_type)); } let ext = Self::from_var(subs, ext_var); diff --git a/compiler/types/src/subs.rs b/compiler/types/src/subs.rs index c1870d3dff..96931dfa5b 100644 --- a/compiler/types/src/subs.rs +++ b/compiler/types/src/subs.rs @@ -1,5 +1,5 @@ use crate::boolean_algebra; -use crate::types::{name_type_var, ErrorType, Problem, TypeExt}; +use crate::types::{name_type_var, ErrorType, Problem, RecordField, TypeExt}; use roc_collections::all::{ImMap, ImSet, MutMap, MutSet, SendMap}; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -567,7 +567,7 @@ impl Content { pub enum FlatType { Apply(Symbol, Vec), Func(Vec, Variable), - Record(MutMap, Variable), + Record(MutMap>, Variable), TagUnion(MutMap>, Variable), RecursiveTagUnion(Variable, MutMap>, Variable), Erroneous(Problem), @@ -612,7 +612,11 @@ fn occurs( short_circuit(subs, root_var, &new_seen, it) } Record(vars_by_field, ext_var) => { - let it = once(&ext_var).chain(vars_by_field.values()); + let it = + once(&ext_var).chain(vars_by_field.values().map(|field| match field { + RecordField::Optional(var) => var, + RecordField::Required(var) => var, + })); short_circuit(subs, root_var, &new_seen, it) } TagUnion(tags, ext_var) => { @@ -729,8 +733,17 @@ fn explicit_substitute( Record(mut vars_by_field, ext_var) => { let new_ext_var = explicit_substitute(subs, from, to, ext_var, seen); - for (_, var) in vars_by_field.iter_mut() { - *var = explicit_substitute(subs, from, to, *var, seen); + for (_, field) in vars_by_field.iter_mut() { + use RecordField::*; + + *field = match field { + Optional(var) => { + Optional(explicit_substitute(subs, from, to, *var, seen)) + } + Required(var) => { + Required(explicit_substitute(subs, from, to, *var, seen)) + } + }; } subs.set_content(in_var, Structure(Record(vars_by_field, new_ext_var))); } @@ -805,8 +818,8 @@ fn get_var_names( vars_by_field .into_iter() - .fold(taken_names, |answer, (_, arg_var)| { - get_var_names(subs, arg_var, answer) + .fold(taken_names, |answer, (_, field)| { + get_var_names(subs, field.into_inner(), answer) }) } FlatType::TagUnion(tags, ext_var) => { @@ -998,8 +1011,15 @@ fn flat_type_to_err_type( Record(vars_by_field, ext_var) => { let mut err_fields = SendMap::default(); - for (field, var) in vars_by_field.into_iter() { - err_fields.insert(field, var_to_err_type(subs, state, var)); + for (field, field_var) in vars_by_field.into_iter() { + use RecordField::*; + + let err_type = match field_var { + Optional(var) => Optional(var_to_err_type(subs, state, var)), + Required(var) => Required(var_to_err_type(subs, state, var)), + }; + + err_fields.insert(field, err_type); } match var_to_err_type(subs, state, ext_var).unwrap_alias() { @@ -1133,8 +1153,8 @@ fn restore_content(subs: &mut Subs, content: &Content) { EmptyTagUnion => (), Record(fields, ext_var) => { - for var in fields.values() { - subs.restore(*var); + for field in fields.values() { + subs.restore(field.into_inner()); } subs.restore(*ext_var); diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index daed54a647..4d753d72c1 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -13,13 +13,95 @@ pub const TYPE_NUM: &str = "Num"; pub const TYPE_INTEGER: &str = "Integer"; pub const TYPE_FLOATINGPOINT: &str = "FloatingPoint"; +#[derive(PartialEq, Eq, Clone)] +pub enum RecordField { + Optional(T), + Required(T), +} + +impl Copy for RecordField {} + +impl fmt::Debug for RecordField { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + use RecordField::*; + + match self { + Optional(typ) => typ.fmt(f), + Required(typ) => typ.fmt(f), + } + } +} + +impl RecordField { + pub fn into_inner(self) -> T { + use RecordField::*; + + match self { + Optional(t) => t, + Required(t) => t, + } + } +} + +impl RecordField { + pub fn substitute(&mut self, substitutions: &ImMap) { + use RecordField::*; + + match self { + Optional(typ) => typ.substitute(substitutions), + Required(typ) => typ.substitute(substitutions), + } + } + + pub fn substitute_alias(&mut self, rep_symbol: Symbol, actual: &Type) { + use RecordField::*; + + match self { + Optional(typ) => typ.substitute_alias(rep_symbol, actual), + Required(typ) => typ.substitute_alias(rep_symbol, actual), + } + } + + pub fn instantiate_aliases( + &mut self, + region: Region, + aliases: &ImMap, + var_store: &mut VarStore, + introduced: &mut ImSet, + ) { + use RecordField::*; + + match self { + Optional(typ) => typ.instantiate_aliases(region, aliases, var_store, introduced), + Required(typ) => typ.instantiate_aliases(region, aliases, var_store, introduced), + } + } + + pub fn contains_symbol(&self, rep_symbol: Symbol) -> bool { + use RecordField::*; + + match self { + Optional(typ) => typ.contains_symbol(rep_symbol), + Required(typ) => typ.contains_symbol(rep_symbol), + } + } + pub fn contains_variable(&self, rep_variable: Variable) -> bool { + use RecordField::*; + + match self { + Optional(typ) => typ.contains_variable(rep_variable), + Required(typ) => typ.contains_variable(rep_variable), + } + } +} + #[derive(PartialEq, Eq, Clone)] pub enum Type { EmptyRec, EmptyTagUnion, /// A function. The types of its arguments, then the type of its return value. Function(Vec, Box), - Record(SendMap, Box), + Record(SendMap>, Box), TagUnion(Vec<(TagName, Vec)>, Box), Alias(Symbol, Vec<(Lowercase, Type)>, Box), RecursiveTagUnion(Variable, Vec<(TagName, Vec)>, Box), @@ -615,7 +697,14 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet) { Record(fields, ext) => { symbols_help(&ext, accum); - fields.values().for_each(|arg| symbols_help(arg, accum)); + fields.values().for_each(|field| { + use RecordField::*; + + match field { + Optional(arg) => symbols_help(arg, accum), + Required(arg) => symbols_help(arg, accum), + } + }); } Alias(alias_symbol, _, actual_type) => { accum.insert(*alias_symbol); @@ -650,8 +739,13 @@ fn variables_help(tipe: &Type, accum: &mut ImSet) { variables_help(ret, accum); } Record(fields, ext) => { - for (_, x) in fields { - variables_help(x, accum); + use RecordField::*; + + for (_, field) in fields { + match field { + Optional(x) => variables_help(x, accum), + Required(x) => variables_help(x, accum), + }; } variables_help(ext, accum); } @@ -720,7 +814,7 @@ fn find_rec_var_uniqueness( } pub struct RecordStructure { - pub fields: MutMap, + pub fields: MutMap>, pub ext: Variable, } @@ -855,7 +949,7 @@ pub enum ErrorType { Type(Symbol, Vec), FlexVar(Lowercase), RigidVar(Lowercase), - Record(SendMap, TypeExt), + Record(SendMap>, TypeExt), TagUnion(SendMap>, TypeExt), RecursiveTagUnion(Box, SendMap>, TypeExt), Function(Vec, Box), @@ -973,9 +1067,22 @@ fn write_error_type_help( Record(fields, ext) => { buf.push('{'); - for (label, content) in fields { + for (label, field) in fields { + use RecordField::*; + buf.push_str(label.as_str()); - buf.push_str(": "); + + let content = match field { + Optional(content) => { + buf.push_str(" ? "); + content + } + Required(content) => { + buf.push_str(" : "); + content + } + }; + write_error_type_help(home, interns, content, buf, Parens::Unnecessary); } @@ -1100,9 +1207,22 @@ fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens: Record(fields, ext) => { buf.push('{'); - for (label, content) in fields { + for (label, field) in fields { + use RecordField::*; + buf.push_str(label.as_str()); - buf.push_str(": "); + + let content = match field { + Optional(content) => { + buf.push_str(" ? "); + content + } + Required(content) => { + buf.push_str(" : "); + content + } + }; + write_debug_error_type_help(content, buf, Parens::Unnecessary); } @@ -1204,7 +1324,7 @@ pub fn name_type_var(letters_used: u32, taken: &mut MutSet) -> (Lower pub fn gather_fields( subs: &Subs, - fields: MutMap, + fields: MutMap>, var: Variable, ) -> RecordStructure { use crate::subs::Content::*; From 43bca05cdb4ff2738fc7ab85d49dcbe22d5c6e1f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 12:42:04 -0400 Subject: [PATCH 27/68] Change guard to DestructType --- compiler/can/src/expr.rs | 6 ++++- compiler/can/src/pattern.rs | 19 ++++++++++---- compiler/fmt/src/annotation.rs | 48 ++++++++++++++++++++++++++++------ compiler/fmt/src/pattern.rs | 12 +++++++-- 4 files changed, 69 insertions(+), 16 deletions(-) diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index eedbf56ccc..21e031270d 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -941,7 +941,7 @@ fn canonicalize_field<'a>( match field { // Both a label and a value, e.g. `{ name: "blah" }` - LabeledValue(label, _, loc_expr) => { + RequiredValue(label, _, loc_expr) => { let field_var = var_store.fresh(); let (loc_can_expr, output) = canonicalize_expr(env, var_store, scope, loc_expr.region, &loc_expr.value); @@ -954,6 +954,10 @@ fn canonicalize_field<'a>( ) } + OptionalValue(_, _, _) => { + todo!("TODO gracefully handle an optional field being used in an Expr"); + } + // A label with no value, e.g. `{ name }` (this is sugar for { name: name }) LabelOnly(_) => { panic!("Somehow a LabelOnly record field was not desugared!"); diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index 7ec9b20fe7..ecc40c7652 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -44,7 +44,14 @@ pub struct RecordDestruct { pub var: Variable, pub label: Lowercase, pub symbol: Symbol, - pub guard: Option<(Variable, Located)>, + pub typ: DestructType, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum DestructType { + Required, + Optional(Variable), + Guard(Variable, Located), } pub fn symbols_from_pattern(pattern: &Pattern) -> Vec { @@ -253,7 +260,7 @@ pub fn canonicalize_pattern<'a>( var: var_store.fresh(), label: Lowercase::from(label), symbol, - guard: None, + typ: DestructType::Required, }, }); } @@ -271,7 +278,8 @@ pub fn canonicalize_pattern<'a>( } }; } - RecordField(label, loc_guard) => { + + RequiredField(label, loc_guard) => { // a guard does not introduce the label into scope! let symbol = scope.ignore(label.into(), &mut env.ident_ids); let can_guard = canonicalize_pattern( @@ -289,7 +297,7 @@ pub fn canonicalize_pattern<'a>( var: var_store.fresh(), label: Lowercase::from(label), symbol, - guard: Some((var_store.fresh(), can_guard)), + typ: DestructType::Guard(var_store.fresh(), can_guard), }, }); } @@ -305,7 +313,8 @@ pub fn canonicalize_pattern<'a>( destructs, }) } - RecordField(_name, _loc_pattern) => { + + RequiredField(_name, _loc_pattern) | OptionalField(_name, _loc_pattern) => { unreachable!("should have been handled in RecordDestructure"); } diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index 5fb9526a44..f7ea0a5b7d 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -248,7 +248,7 @@ impl<'a> Formattable<'a> for AssignedField<'a, TypeAnnotation<'a>> { indent: u16, ) { // we abuse the `Newlines` type to decide between multiline or single-line layout - format_assigned_field_help(self, buf, parens, indent, " : ", newlines == Newlines::Yes); + format_assigned_field_help(self, buf, parens, indent, " ", newlines == Newlines::Yes); } } @@ -265,7 +265,7 @@ impl<'a> Formattable<'a> for AssignedField<'a, Expr<'a>> { indent: u16, ) { // we abuse the `Newlines` type to decide between multiline or single-line layout - format_assigned_field_help(self, buf, parens, indent, ": ", newlines == Newlines::Yes); + format_assigned_field_help(self, buf, parens, indent, "", newlines == Newlines::Yes); } } @@ -273,7 +273,9 @@ fn is_multiline_assigned_field_help<'a, T: Formattable<'a>>(afield: &AssignedFie use self::AssignedField::*; match afield { - LabeledValue(_, spaces, ann) => !spaces.is_empty() || ann.value.is_multiline(), + RequiredValue(_, spaces, ann) | OptionalValue(_, spaces, ann) => { + !spaces.is_empty() || ann.value.is_multiline() + } LabelOnly(_) => false, AssignedField::SpaceBefore(_, _) | AssignedField::SpaceAfter(_, _) => true, Malformed(text) => text.chars().any(|c| c == '\n'), @@ -285,7 +287,7 @@ fn format_assigned_field_help<'a, T>( buf: &mut String<'a>, parens: Parens, indent: u16, - separator: &str, + separator_prefix: &str, is_multiline: bool, ) where T: Formattable<'a>, @@ -293,7 +295,7 @@ fn format_assigned_field_help<'a, T>( use self::AssignedField::*; match zelf { - LabeledValue(name, spaces, ann) => { + RequiredValue(name, spaces, ann) => { if is_multiline { newline(buf, indent); } @@ -304,7 +306,23 @@ fn format_assigned_field_help<'a, T>( fmt_spaces(buf, spaces.iter(), indent); } - buf.push_str(separator); + buf.push_str(separator_prefix); + buf.push(':'); + ann.value.format(buf, indent); + } + OptionalValue(name, spaces, ann) => { + if is_multiline { + newline(buf, indent); + } + + buf.push_str(name.value); + + if !spaces.is_empty() { + fmt_spaces(buf, spaces.iter(), indent); + } + + buf.push_str(separator_prefix); + buf.push('?'); ann.value.format(buf, indent); } LabelOnly(name) => { @@ -316,10 +334,24 @@ fn format_assigned_field_help<'a, T>( } AssignedField::SpaceBefore(sub_field, spaces) => { fmt_comments_only(buf, spaces.iter(), indent); - format_assigned_field_help(sub_field, buf, parens, indent, separator, is_multiline); + format_assigned_field_help( + sub_field, + buf, + parens, + indent, + separator_prefix, + is_multiline, + ); } AssignedField::SpaceAfter(sub_field, spaces) => { - format_assigned_field_help(sub_field, buf, parens, indent, separator, is_multiline); + format_assigned_field_help( + sub_field, + buf, + parens, + indent, + separator_prefix, + is_multiline, + ); fmt_comments_only(buf, spaces.iter(), indent); } Malformed(raw) => { diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index ac35bcd585..b43f27ac27 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -25,7 +25,9 @@ impl<'a> Formattable<'a> for Pattern<'a> { Pattern::Nested(nested_pat) => nested_pat.is_multiline(), Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()), - Pattern::RecordField(_, subpattern) => subpattern.is_multiline(), + Pattern::RequiredField(_, subpattern) | Pattern::OptionalField(_, subpattern) => { + subpattern.is_multiline() + } Pattern::Identifier(_) | Pattern::GlobalTag(_) @@ -92,12 +94,18 @@ impl<'a> Formattable<'a> for Pattern<'a> { buf.push_str(" }"); } - RecordField(name, loc_pattern) => { + RequiredField(name, loc_pattern) => { buf.push_str(name); buf.push_str(": "); loc_pattern.format(buf, indent); } + OptionalField(name, loc_pattern) => { + buf.push_str(name); + buf.push_str(" ? "); + loc_pattern.format(buf, indent); + } + NumLiteral(string) => buf.push_str(string), NonBase10Literal { base, From 1229b0ea5f73d073f3434ab4881c76be3298b846 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 12:50:14 -0400 Subject: [PATCH 28/68] Unify optional fields --- compiler/unify/src/unify.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 31b142cc56..7ff9edfd2d 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -4,7 +4,7 @@ use roc_module::symbol::Symbol; use roc_types::boolean_algebra::Bool; use roc_types::subs::Content::{self, *}; use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable}; -use roc_types::types::{gather_fields, ErrorType, Mismatch, RecordStructure}; +use roc_types::types::{gather_fields, ErrorType, Mismatch, RecordField, RecordStructure}; macro_rules! mismatch { () => {{ @@ -319,17 +319,27 @@ fn unify_shared_fields( subs: &mut Subs, pool: &mut Pool, ctx: &Context, - shared_fields: MutMap, - other_fields: MutMap, + shared_fields: MutMap, RecordField)>, + other_fields: MutMap>, ext: Variable, ) -> Outcome { let mut matching_fields = MutMap::default(); let num_shared_fields = shared_fields.len(); for (name, (actual, expected)) in shared_fields { - let problems = unify_pool(subs, pool, actual, expected); + let problems = unify_pool(subs, pool, actual.into_inner(), expected.into_inner()); if problems.is_empty() { + use RecordField::*; + + // If either field is Required, both are Required. + let actual = match (actual, expected) { + (Required(val), Required(_)) => Required(val), + (Required(val), Optional(_)) => Required(val), + (Optional(val), Required(_)) => Required(val), + (Optional(val), Optional(_)) => Optional(val), + }; + matching_fields.insert(name, actual); } } From b6f8143d4c753d5b085150ac06a9256e33e240f8 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 12:54:57 -0400 Subject: [PATCH 29/68] Canonicalize optional record fields --- compiler/constrain/src/expr.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index bb5b292e92..a4fa372dfb 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -16,7 +16,7 @@ use roc_region::all::{Located, Region}; use roc_types::subs::Variable; use roc_types::types::AnnotationSource::{self, *}; use roc_types::types::Type::{self, *}; -use roc_types::types::{Alias, Category, PReason, Reason}; +use roc_types::types::{Alias, Category, PReason, Reason, RecordField}; /// This is for constraining Defs #[derive(Default, Debug)] @@ -110,7 +110,7 @@ pub fn constrain_expr( field_vars.push(field_var); field_exprs.insert(label.clone(), loc_field_expr); - field_types.insert(label.clone(), field_type); + field_types.insert(label.clone(), RecordField::Required(field_type)); constraints.push(field_con); } @@ -145,7 +145,7 @@ pub fn constrain_expr( symbol, updates, } => { - let mut fields: SendMap = SendMap::default(); + let mut fields: SendMap> = SendMap::default(); let mut vars = Vec::with_capacity(updates.len() + 2); let mut cons = Vec::with_capacity(updates.len() + 1); for (field_name, Field { var, loc_expr, .. }) in updates.clone() { @@ -156,7 +156,7 @@ pub fn constrain_expr( field_name.clone(), &loc_expr, ); - fields.insert(field_name, tipe); + fields.insert(field_name, RecordField::Required(tipe)); vars.push(var); cons.push(con); } @@ -618,7 +618,7 @@ pub fn constrain_expr( let mut rec_field_types = SendMap::default(); let label = field.clone(); - rec_field_types.insert(label, field_type.clone()); + rec_field_types.insert(label, RecordField::Required(field_type.clone())); let record_type = Type::Record(rec_field_types, Box::new(ext_type)); let record_expected = Expected::NoExpectation(record_type); @@ -664,7 +664,7 @@ pub fn constrain_expr( let mut field_types = SendMap::default(); let label = field.clone(); - field_types.insert(label, field_type.clone()); + field_types.insert(label, RecordField::Required(field_type.clone())); let record_type = Type::Record(field_types, Box::new(ext_type)); let category = Category::Accessor(field.clone()); From 71ef37923acbe69abe2bb6072cbe5f8bb0f731d3 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 13:12:04 -0400 Subject: [PATCH 30/68] Constrain optional record fields --- compiler/constrain/src/module.rs | 13 ++++-- compiler/constrain/src/pattern.rs | 69 ++++++++++++++++++++----------- 2 files changed, 55 insertions(+), 27 deletions(-) diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index 10668956c0..63914b6774 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -9,7 +9,7 @@ use roc_region::all::{Located, Region}; use roc_types::boolean_algebra::Bool; use roc_types::solved_types::{BuiltinAlias, SolvedBool, SolvedType}; use roc_types::subs::{VarId, VarStore, Variable}; -use roc_types::types::{Alias, Problem, Type}; +use roc_types::types::{Alias, Problem, RecordField, Type}; pub type SubsByModule = MutMap; @@ -214,10 +214,17 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut V Type::Variable(var) } Record { fields, ext } => { + use RecordField::*; + let mut new_fields = SendMap::default(); - for (label, typ) in fields { - new_fields.insert(label.clone(), to_type(&typ, free_vars, var_store)); + for (label, field) in fields { + let field_val = match field { + Required(typ) => Required(to_type(&typ, free_vars, var_store)), + Optional(typ) => Optional(to_type(&typ, free_vars, var_store)), + }; + + new_fields.insert(label.clone(), field_val); } Type::Record(new_fields, Box::new(to_type(ext, free_vars, var_store))) diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 4beede88d1..21bca628b4 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -2,13 +2,13 @@ use crate::builtins; use roc_can::constraint::Constraint; use roc_can::expected::{Expected, PExpected}; use roc_can::pattern::Pattern::{self, *}; -use roc_can::pattern::RecordDestruct; +use roc_can::pattern::{DestructType, RecordDestruct}; use roc_collections::all::{Index, SendMap}; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; use roc_types::subs::Variable; -use roc_types::types::{Category, PReason, PatternCategory, Type}; +use roc_types::types::{Category, PReason, PatternCategory, RecordField, Type}; pub struct PatternState { pub headers: SendMap>, @@ -61,12 +61,20 @@ fn headers_from_annotation_help( RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() { Type::Record(fields, _) => { - for destruct in destructs { - // NOTE ignores the .guard field. - if let Some(field_type) = fields.get(&destruct.value.label) { + for loc_destruct in destructs { + let destruct = &loc_destruct.value; + + // NOTE: We ignore both Guard and optionality when + // determining the type of the assigned def (which is what + // gets added to the header here). + // + // For example, no matter whether it's `{ x } = rec` or + // `{ x ? 0 } = rec` or `{ x: 5 } -> ...` in all cases + // the type of `x` within the binding itself is the same. + if let Some(field_type) = fields.get(&destruct.label) { headers.insert( - destruct.value.symbol, - Located::at(annotation.region, field_type.clone()), + destruct.symbol, + Located::at(annotation.region, field_type.clone().into_inner()), ); } else { return false; @@ -179,7 +187,7 @@ pub fn constrain_pattern( state.vars.push(*ext_var); let ext_type = Type::Variable(*ext_var); - let mut field_types: SendMap = SendMap::default(); + let mut field_types: SendMap> = SendMap::default(); for Located { value: @@ -187,7 +195,7 @@ pub fn constrain_pattern( var, label, symbol, - guard, + typ, }, .. } in destructs @@ -201,23 +209,36 @@ pub fn constrain_pattern( .insert(*symbol, Located::at(region, pat_type.clone())); } - field_types.insert(label.clone(), pat_type.clone()); + let field_type = match typ { + DestructType::Guard(guard_var, loc_guard) => { + state.constraints.push(Constraint::Pattern( + region, + PatternCategory::PatternGuard, + Type::Variable(*guard_var), + PExpected::ForReason( + PReason::PatternGuard, + pat_type.clone(), + loc_guard.region, + ), + )); + state.vars.push(*guard_var); - if let Some((guard_var, loc_guard)) = guard { - state.constraints.push(Constraint::Pattern( - region, - PatternCategory::PatternGuard, - Type::Variable(*guard_var), - PExpected::ForReason( - PReason::PatternGuard, - pat_type.clone(), - loc_guard.region, - ), - )); - state.vars.push(*guard_var); + constrain_pattern(&loc_guard.value, loc_guard.region, expected, state); - constrain_pattern(&loc_guard.value, loc_guard.region, expected, state); - } + RecordField::Required(pat_type) + } + DestructType::Optional(_var) => { + todo!("Add a constraint for the default value."); + + // RecordField::Optional(pat_type) + } + DestructType::Required => { + // No extra constraints necessary. + RecordField::Required(pat_type) + } + }; + + field_types.insert(label.clone(), field_type); state.vars.push(*var); } From a11b7ab4f50266752925e87821a80f18206c400b Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 13:44:22 -0400 Subject: [PATCH 31/68] Constrain uniquness for optional fields --- compiler/constrain/src/uniq.rs | 101 +++++++++++++++++++++++---------- 1 file changed, 72 insertions(+), 29 deletions(-) diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index 39e31f4cf9..2addd2522e 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -5,7 +5,7 @@ use roc_can::constraint::LetConstraint; use roc_can::def::{Declaration, Def}; use roc_can::expected::{Expected, PExpected}; use roc_can::expr::{Expr, Field, WhenBranch}; -use roc_can::pattern::{Pattern, RecordDestruct}; +use roc_can::pattern::{DestructType, Pattern, RecordDestruct}; use roc_collections::all::{ImMap, ImSet, Index, SendMap}; use roc_module::ident::{Ident, Lowercase}; use roc_module::symbol::{ModuleId, Symbol}; @@ -14,7 +14,7 @@ use roc_types::boolean_algebra::Bool; use roc_types::subs::{VarStore, Variable}; use roc_types::types::AnnotationSource::{self, *}; use roc_types::types::Type::{self, *}; -use roc_types::types::{Alias, Category, PReason, Reason}; +use roc_types::types::{Alias, Category, PReason, Reason, RecordField}; use roc_uniq::builtins::{attr_type, empty_list_type, list_type, str_type}; use roc_uniq::sharing::{self, FieldAccess, Mark, Usage, VarUsage}; @@ -213,14 +213,14 @@ fn constrain_pattern( state.vars.push(*ext_var); let ext_type = Type::Variable(*ext_var); - let mut field_types: SendMap = SendMap::default(); + let mut field_types: SendMap> = SendMap::default(); for Located { value: RecordDestruct { var, label, symbol, - guard, + typ, }, .. } in destructs @@ -237,18 +237,31 @@ fn constrain_pattern( .insert(*symbol, Located::at(pattern.region, pat_type.clone())); } - field_types.insert(label.clone(), pat_type.clone()); + let field_type = match typ { + DestructType::Guard(guard_var, loc_guard) => { + state.constraints.push(Constraint::Pattern( + pattern.region, + PatternCategory::PatternGuard, + Type::Variable(*guard_var), + PExpected::NoExpectation(pat_type.clone()), + )); + state.vars.push(*guard_var); + constrain_pattern(var_store, state, loc_guard, expected); - if let Some((guard_var, loc_guard)) = guard { - state.constraints.push(Constraint::Pattern( - pattern.region, - PatternCategory::PatternGuard, - Type::Variable(*guard_var), - PExpected::NoExpectation(pat_type.clone()), - )); - state.vars.push(*guard_var); - constrain_pattern(var_store, state, loc_guard, expected); - } + RecordField::Required(pat_type) + } + DestructType::Optional(_var) => { + todo!("Add a constraint for the default value."); + + // RecordField::Optional(pat_type) + } + DestructType::Required => { + // No extra constraints necessary. + RecordField::Required(pat_type) + } + }; + + field_types.insert(label.clone(), field_type); state.vars.push(*var); } @@ -495,7 +508,7 @@ pub fn constrain_expr( ); field_vars.push(field_var); - field_types.insert(label.clone(), field_type); + field_types.insert(label.clone(), RecordField::Required(field_type)); constraints.push(field_con); } @@ -1224,7 +1237,7 @@ pub fn constrain_expr( symbol, updates, } => { - let mut fields: SendMap = SendMap::default(); + let mut fields: SendMap> = SendMap::default(); let mut vars = Vec::with_capacity(updates.len() + 2); let mut cons = Vec::with_capacity(updates.len() + 3); for (field_name, Field { var, loc_expr, .. }) in updates.clone() { @@ -1238,7 +1251,7 @@ pub fn constrain_expr( field_name.clone(), &loc_expr, ); - fields.insert(field_name, tipe); + fields.insert(field_name, RecordField::Required(tipe)); vars.push(var); cons.push(con); } @@ -1300,7 +1313,7 @@ pub fn constrain_expr( let field_uniq_type = Bool::variable(field_uniq_var); let field_type = attr_type(field_uniq_type, Type::Variable(*field_var)); - field_types.insert(field.clone(), field_type.clone()); + field_types.insert(field.clone(), RecordField::Required(field_type.clone())); let record_uniq_var = var_store.fresh(); let record_uniq_type = Bool::container(record_uniq_var, vec![field_uniq_var]); @@ -1357,7 +1370,7 @@ pub fn constrain_expr( let field_uniq_type = Bool::variable(field_uniq_var); let field_type = attr_type(field_uniq_type, Type::Variable(*field_var)); - field_types.insert(field.clone(), field_type.clone()); + field_types.insert(field.clone(), RecordField::Required(field_type.clone())); let record_uniq_var = var_store.fresh(); let record_uniq_type = Bool::container(record_uniq_var, vec![field_uniq_var]); @@ -1589,8 +1602,11 @@ fn constrain_by_usage_record( let field_type = attr_type(Bool::Shared, nested_type); - field_types.insert(lowercase.clone(), field_type); + // In expressions, it's only possible to do record access on + // Required fields. + field_types.insert(lowercase.clone(), RecordField::Required(field_type)); } + ( Bool::Shared, Type::Record( @@ -1622,8 +1638,11 @@ fn constrain_by_usage_record( Bool::Shared => attr_type(Bool::Shared, nested_type), }; - field_types.insert(lowercase.clone(), field_type); + // In expressions, it's only possible to do record access on + // Required fields. + field_types.insert(lowercase.clone(), RecordField::Required(field_type)); } + ( Bool::container(record_uniq_var, uniq_vars), Type::Record( @@ -1828,10 +1847,28 @@ fn annotation_to_attr_type( let mut vars = Vec::with_capacity(fields.len()); let mut lifted_fields = SendMap::default(); - for (label, tipe) in fields.clone() { - let (new_vars, lifted_field) = - annotation_to_attr_type(var_store, &tipe, rigids, change_var_kind); - vars.extend(new_vars); + for (label, field) in fields.clone() { + use RecordField::*; + + let lifted_field = match field { + Required(tipe) => { + let (new_vars, lifted_field) = + annotation_to_attr_type(var_store, &tipe, rigids, change_var_kind); + + vars.extend(new_vars); + + Required(lifted_field) + } + Optional(tipe) => { + let (new_vars, lifted_field) = + annotation_to_attr_type(var_store, &tipe, rigids, change_var_kind); + + vars.extend(new_vars); + + Optional(lifted_field) + } + }; + lifted_fields.insert(label, lifted_field); } @@ -2425,9 +2462,15 @@ fn fix_mutual_recursive_alias_help_help(rec_var: Variable, attribute: &Type, int Record(fields, ext) => { fix_mutual_recursive_alias_help(rec_var, attribute, ext); - fields - .iter_mut() - .for_each(|arg| fix_mutual_recursive_alias_help(rec_var, attribute, arg)); + + for field in fields.iter_mut() { + let arg = match field { + RecordField::Required(arg) => arg, + RecordField::Optional(arg) => arg, + }; + + fix_mutual_recursive_alias_help(rec_var, attribute, arg); + } } Alias(_, _, actual_type) => { // call help_help, because actual_type is not wrapped in ATTR From 4e2cd3fefca181dad76e027a8bffa8fd806a152d Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 13:49:06 -0400 Subject: [PATCH 32/68] Solve optional fields --- compiler/solve/src/solve.rs | 37 +++++++++++++++++++++++++++++-------- 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/compiler/solve/src/solve.rs b/compiler/solve/src/solve.rs index 961c3d3fde..c3976275c1 100644 --- a/compiler/solve/src/solve.rs +++ b/compiler/solve/src/solve.rs @@ -8,7 +8,7 @@ use roc_types::boolean_algebra::{self, Bool}; use roc_types::solved_types::Solved; use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable}; use roc_types::types::Type::{self, *}; -use roc_types::types::{Alias, Category, ErrorType, PatternCategory}; +use roc_types::types::{Alias, Category, ErrorType, PatternCategory, RecordField}; use roc_unify::unify::unify; use roc_unify::unify::Unified::*; @@ -615,10 +615,14 @@ fn type_to_variable( let mut field_vars = MutMap::default(); for (field, field_type) in fields { - field_vars.insert( - field.clone(), - type_to_variable(subs, rank, pools, cached, field_type), - ); + use RecordField::*; + + let field_var = match field_type { + Required(typ) => Required(type_to_variable(subs, rank, pools, cached, typ)), + Optional(typ) => Optional(type_to_variable(subs, rank, pools, cached, typ)), + }; + + field_vars.insert(field.clone(), field_var); } let temp_ext_var = type_to_variable(subs, rank, pools, cached, ext); @@ -1091,7 +1095,13 @@ fn adjust_rank_content( let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, ext_var); for (_, var) in fields { - rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var)); + rank = rank.max(adjust_rank( + subs, + young_mark, + visit_mark, + group_rank, + var.into_inner(), + )); } rank @@ -1243,8 +1253,19 @@ fn deep_copy_var_help( Record(fields, ext_var) => { let mut new_fields = MutMap::default(); - for (label, var) in fields { - new_fields.insert(label, deep_copy_var_help(subs, max_rank, pools, var)); + for (label, field) in fields { + use RecordField::*; + + let new_field = match field { + Required(var) => { + Required(deep_copy_var_help(subs, max_rank, pools, var)) + } + Optional(var) => { + Optional(deep_copy_var_help(subs, max_rank, pools, var)) + } + }; + + new_fields.insert(label, new_field); } Record( From 49e66491ad6c05a7b895eebe5e8ec9f492437bf3 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 13:51:04 -0400 Subject: [PATCH 33/68] Fix a parse test --- compiler/parse/tests/test_parse.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/parse/tests/test_parse.rs b/compiler/parse/tests/test_parse.rs index 63732534f6..519125163e 100644 --- a/compiler/parse/tests/test_parse.rs +++ b/compiler/parse/tests/test_parse.rs @@ -258,12 +258,12 @@ mod test_parse { #[test] fn record_update() { let arena = Bump::new(); - let label1 = LabeledValue( + let label1 = RequiredValue( Located::new(0, 0, 16, 17, "x"), &[], arena.alloc(Located::new(0, 0, 19, 20, Num("5"))), ); - let label2 = LabeledValue( + let label2 = RequiredValue( Located::new(0, 0, 22, 23, "y"), &[], arena.alloc(Located::new(0, 0, 25, 26, Num("0"))), From fdceedda005e8587b957dcb18a40e4d07a7c2f54 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 14:16:46 -0400 Subject: [PATCH 34/68] Fix unifying optional fields --- compiler/solve/tests/solve_expr.rs | 30 ++++++++++++++++++++++++++++++ compiler/unify/src/unify.rs | 15 +++++++++++++-- 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index df9125ceee..76f8296003 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -2565,4 +2565,34 @@ mod solve_expr { "should fail", ); } + + // OPTIONAL RECORD FIELDS + + #[test] + fn optional_field_unifies_with_missing() { + infer_eq_without_problem( + indoc!( + r#" + negatePoint : { x : Int, y : Int, z ? Num c } -> { x : Int, y : Int, z : Num c } + + negatePoint { x: 1, y: 2 } + "# + ), + "{ x : Int, y : Int, z : Num c }", + ); + } + + #[test] + fn optional_field_unifies_with_present() { + infer_eq_without_problem( + indoc!( + r#" + negatePoint : { x : Num a, y : Num b, z ? c } -> { x : Num a, y : Num b, z : c } + + negatePoint { x: 1, y: 2.1, z: 0x3 } + "# + ), + "{ x : Num a, y : Float, z : Int }", + ); + } } diff --git a/compiler/unify/src/unify.rs b/compiler/unify/src/unify.rs index 7ff9edfd2d..78c84c1a1f 100644 --- a/compiler/unify/src/unify.rs +++ b/compiler/unify/src/unify.rs @@ -614,6 +614,17 @@ fn unify_shared_tags( } } +fn has_no_required_fields<'a, I, T>(fields: &mut I) -> bool +where + I: Iterator>, + T: 'a, +{ + fields.all(|field| match field { + RecordField::Required(_) => false, + RecordField::Optional(_) => true, + }) +} + #[inline(always)] fn unify_flat_type( subs: &mut Subs, @@ -627,11 +638,11 @@ fn unify_flat_type( match (left, right) { (EmptyRecord, EmptyRecord) => merge(subs, ctx, Structure(left.clone())), - (Record(fields, ext), EmptyRecord) if fields.is_empty() => { + (Record(fields, ext), EmptyRecord) if has_no_required_fields(&mut fields.values()) => { unify_pool(subs, pool, *ext, ctx.second) } - (EmptyRecord, Record(fields, ext)) if fields.is_empty() => { + (EmptyRecord, Record(fields, ext)) if has_no_required_fields(&mut fields.values()) => { unify_pool(subs, pool, ctx.first, *ext) } From 872f4b3541623d6bb52c4eb1cdcbf3b08f156c4e Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 14:33:25 -0400 Subject: [PATCH 35/68] Add some solving tests for optional fields --- compiler/solve/tests/solve_expr.rs | 34 ++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 76f8296003..6a43144dbd 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -2582,6 +2582,23 @@ mod solve_expr { ); } + #[test] + fn open_optional_field_unifies_with_missing() { + infer_eq_without_problem( + indoc!( + r#" + negatePoint : { x : Int, y : Int, z ? Num c }r -> { x : Int, y : Int, z : Num c }r + + a = negatePoint { x: 1, y: 2 } + b = negatePoint { x: 1, y: 2, blah : "hi" } + + { a, b } + "# + ), + "{ a : { x : Int, y : Int, z : Num c }, b : { blah : Str, x : Int, y : Int, z : Num c } }", + ); + } + #[test] fn optional_field_unifies_with_present() { infer_eq_without_problem( @@ -2595,4 +2612,21 @@ mod solve_expr { "{ x : Num a, y : Float, z : Int }", ); } + + #[test] + fn open_optional_field_unifies_with_present() { + infer_eq_without_problem( + indoc!( + r#" + negatePoint : { x : Num a, y : Num b, z ? c }r -> { x : Num a, y : Num b, z : c }r + + a = negatePoint { x: 1, y: 2.1 } + b = negatePoint { x: 1, y: 2.1, blah : "hi" } + + { a, b } + "# + ), + "{ a : { x : Num a, y : Float, z : c }, b : { blah : Str, x : Num a, y : Float, z : c } }", + ); + } } From 28a2f9656edc62d73501bdcc21cf28f4818e90d0 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 14:51:18 -0400 Subject: [PATCH 36/68] Constrain optional fields --- compiler/can/src/pattern.rs | 3 ++- compiler/constrain/src/expr.rs | 12 ++++++++++-- compiler/constrain/src/pattern.rs | 30 ++++++++++++++++++++++++------ compiler/constrain/src/uniq.rs | 2 +- compiler/types/src/types.rs | 2 ++ 5 files changed, 39 insertions(+), 10 deletions(-) diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index ecc40c7652..a8bc6ba307 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -1,4 +1,5 @@ use crate::env::Env; +use crate::expr::Expr; use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use crate::scope::Scope; use roc_module::ident::{Ident, Lowercase, TagName}; @@ -50,7 +51,7 @@ pub struct RecordDestruct { #[derive(Clone, Debug, PartialEq)] pub enum DestructType { Required, - Optional(Variable), + Optional(Variable, Located), Guard(Variable, Located), } diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index a4fa372dfb..a646e03275 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -327,6 +327,7 @@ pub fn constrain_expr( pattern_types.push(pattern_type); constrain_pattern( + env, &loc_pattern.value, loc_pattern.region, pattern_expected, @@ -843,6 +844,7 @@ fn constrain_when_branch( // then unify that variable with the expectation? for loc_pattern in &when_branch.patterns { constrain_pattern( + env, &loc_pattern.value, loc_pattern.region, pattern_expected.clone(), @@ -947,7 +949,11 @@ pub fn constrain_decls( constraint } -fn constrain_def_pattern(loc_pattern: &Located, expr_type: Type) -> PatternState { +fn constrain_def_pattern( + env: &Env, + loc_pattern: &Located, + expr_type: Type, +) -> PatternState { let pattern_expected = PExpected::NoExpectation(expr_type); let mut state = PatternState { @@ -957,6 +963,7 @@ fn constrain_def_pattern(loc_pattern: &Located, expr_type: Type) -> Pat }; constrain_pattern( + env, &loc_pattern.value, loc_pattern.region, pattern_expected, @@ -970,7 +977,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { let expr_var = def.expr_var; let expr_type = Type::Variable(expr_var); - let mut pattern_state = constrain_def_pattern(&def.loc_pattern, expr_type.clone()); + let mut pattern_state = constrain_def_pattern(env, &def.loc_pattern, expr_type.clone()); pattern_state.vars.push(expr_var); @@ -1117,6 +1124,7 @@ pub fn rec_defs_help( }; constrain_pattern( + env, &def.loc_pattern.value, def.loc_pattern.region, pattern_expected, diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 21bca628b4..25db18453a 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -1,4 +1,5 @@ use crate::builtins; +use crate::expr::{constrain_expr, Env}; use roc_can::constraint::Constraint; use roc_can::expected::{Expected, PExpected}; use roc_can::pattern::Pattern::{self, *}; @@ -8,7 +9,7 @@ use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; use roc_region::all::{Located, Region}; use roc_types::subs::Variable; -use roc_types::types::{Category, PReason, PatternCategory, RecordField, Type}; +use roc_types::types::{Category, PReason, PatternCategory, Reason, RecordField, Type}; pub struct PatternState { pub headers: SendMap>, @@ -120,6 +121,7 @@ fn headers_from_annotation_help( /// intiialize the Vecs in PatternState using with_capacity /// based on its knowledge of their lengths. pub fn constrain_pattern( + env: &Env, pattern: &Pattern, region: Region, expected: PExpected, @@ -223,14 +225,30 @@ pub fn constrain_pattern( )); state.vars.push(*guard_var); - constrain_pattern(&loc_guard.value, loc_guard.region, expected, state); + constrain_pattern(env, &loc_guard.value, loc_guard.region, expected, state); RecordField::Required(pat_type) } - DestructType::Optional(_var) => { - todo!("Add a constraint for the default value."); + DestructType::Optional(expr_var, loc_expr) => { + // Eq(Type, Expected, Category, Region), + let expr_expected = Expected::ForReason( + Reason::RecordDefaultField(label.clone()), + pat_type.clone(), + loc_expr.region, + ); - // RecordField::Optional(pat_type) + state.constraints.push(Constraint::Eq( + Type::Variable(*expr_var), + expr_expected.clone(), + Category::DefaultValue(label.clone()), + region, + )); + + state.vars.push(*expr_var); + + constrain_expr(env, loc_expr.region, &loc_expr.value, expr_expected); + + RecordField::Optional(pat_type) } DestructType::Required => { // No extra constraints necessary. @@ -283,7 +301,7 @@ pub fn constrain_pattern( pattern_type, region, ); - constrain_pattern(&loc_pattern.value, loc_pattern.region, expected, state); + constrain_pattern(env, &loc_pattern.value, loc_pattern.region, expected, state); } let whole_con = Constraint::Eq( diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index 2addd2522e..073ec5d01d 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -250,7 +250,7 @@ fn constrain_pattern( RecordField::Required(pat_type) } - DestructType::Optional(_var) => { + DestructType::Optional(_expr_var, _loc_expr) => { todo!("Add a constraint for the default value."); // RecordField::Optional(pat_type) diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index 4d753d72c1..ae41d94814 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -864,6 +864,7 @@ pub enum Reason { }, RecordUpdateValue(Lowercase), RecordUpdateKeys(Symbol, SendMap), + RecordDefaultField(Lowercase), } #[derive(PartialEq, Debug, Clone)] @@ -893,6 +894,7 @@ pub enum Category { Record, Accessor(Lowercase), Access(Lowercase), + DefaultValue(Lowercase), // for setting optional fields } #[derive(Debug, Clone, PartialEq, Eq)] From 2f50c0494bf5c924164b8ad8b3cfa9412c69c837 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 20:16:36 -0400 Subject: [PATCH 37/68] Monomorphize optional fields --- compiler/build/src/program.rs | 3 +- compiler/mono/src/decision_tree.rs | 31 +++++++----- compiler/mono/src/expr.rs | 79 ++++++++++++++++++++++-------- compiler/mono/src/layout.rs | 6 ++- compiler/mono/src/pattern.rs | 7 +-- 5 files changed, 86 insertions(+), 40 deletions(-) diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index 7aa3245d40..d41b89e4d6 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -162,6 +162,7 @@ pub fn build( let mut layout_ids = LayoutIds::default(); let mut procs = Procs::default(); let mut mono_problems = std::vec::Vec::new(); + let mut layout_cache = LayoutCache::default(); let mut mono_env = Env { arena, subs: &mut subs, @@ -191,6 +192,7 @@ pub fn build( procs.insert_named( &mut mono_env, + &mut layout_cache, symbol, annotation, loc_args, @@ -235,7 +237,6 @@ pub fn build( Vec::with_capacity(num_headers) }; - let mut layout_cache = LayoutCache::default(); let mut procs = roc_mono::expr::specialize_all(&mut mono_env, procs, &mut layout_cache); assert_eq!( diff --git a/compiler/mono/src/decision_tree.rs b/compiler/mono/src/decision_tree.rs index a8a5b2324b..ad3e3fef73 100644 --- a/compiler/mono/src/decision_tree.rs +++ b/compiler/mono/src/decision_tree.rs @@ -1,8 +1,5 @@ -use crate::expr::Env; -use crate::expr::Expr; -use crate::expr::Pattern; -use crate::layout::Builtin; -use crate::layout::Layout; +use crate::expr::{DestructType, Env, Expr, Pattern}; +use crate::layout::{Builtin, Layout}; use crate::pattern::{Ctor, RenderAs, TagId, Union}; use bumpalo::Bump; use roc_collections::all::{MutMap, MutSet}; @@ -393,10 +390,16 @@ fn test_at_path<'a>(selected_path: &Path, branch: Branch<'a>, all_tests: &mut Ve let mut arguments = std::vec::Vec::new(); for destruct in destructs { - if let Some(guard) = &destruct.guard { - arguments.push((guard.clone(), destruct.layout.clone())); - } else { - arguments.push((Pattern::Underscore, destruct.layout.clone())); + match &destruct.typ { + DestructType::Guard(guard) => { + arguments.push((guard.clone(), destruct.layout.clone())); + } + DestructType::Required => { + arguments.push((Pattern::Underscore, destruct.layout.clone())); + } + DestructType::Optional(_expr) => { + todo!("test_at_type for optional destruct"); + } } } @@ -524,10 +527,12 @@ fn to_relevant_branch_help<'a>( } => { debug_assert!(test_name == &TagName::Global("#Record".into())); let sub_positions = destructs.into_iter().enumerate().map(|(index, destruct)| { - let pattern = if let Some(guard) = destruct.guard { - guard.clone() - } else { - Pattern::Underscore + let pattern = match destruct.typ { + DestructType::Guard(guard) => guard.clone(), + DestructType::Required => Pattern::Underscore, + DestructType::Optional(_expr) => { + todo!("TODO decision tree for optional field branch"); + } }; ( diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index cbcc638e61..f8313d333b 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -11,7 +11,6 @@ use roc_problem::can::RuntimeError; use roc_region::all::{Located, Region}; use roc_types::subs::{Content, FlatType, Subs, Variable}; use std::collections::HashMap; -use std::hash::Hash; #[derive(Clone, Debug, PartialEq)] pub struct PartialProc<'a> { @@ -56,13 +55,14 @@ impl<'a> Procs<'a> { pub fn insert_named( &mut self, env: &mut Env<'a, '_>, + layout_cache: &mut LayoutCache<'a>, name: Symbol, annotation: Variable, loc_args: std::vec::Vec<(Variable, Located)>, loc_body: Located, ret_var: Variable, ) { - match patterns_to_when(env, loc_args, ret_var, loc_body) { + match patterns_to_when(env, self, layout_cache, loc_args, ret_var, loc_body) { Ok((_, pattern_symbols, body)) => { // a named closure. Since these aren't specialized by the surrounding // context, we can't add pending specializations for them yet. @@ -106,7 +106,7 @@ impl<'a> Procs<'a> { ret_var: Variable, layout_cache: &mut LayoutCache<'a>, ) -> Result, RuntimeError> { - match patterns_to_when(env, loc_args, ret_var, loc_body) { + match patterns_to_when(env, self, layout_cache, loc_args, ret_var, loc_body) { Ok((pattern_vars, pattern_symbols, body)) => { // an anonymous closure. These will always be specialized already // by the surrounding context, so we can add pending specializations @@ -418,6 +418,8 @@ fn num_argument_to_int_or_float(subs: &Subs, var: Variable) -> IntOrFloat { #[allow(clippy::type_complexity)] fn patterns_to_when<'a>( env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, patterns: std::vec::Vec<(Variable, Located)>, body_var: Variable, body: Located, @@ -438,7 +440,7 @@ fn patterns_to_when<'a>( // are only stores anyway, no branches. for (pattern_var, pattern) in patterns.into_iter() { let context = crate::pattern::Context::BadArg; - let mono_pattern = from_can_pattern(env, &pattern.value); + let mono_pattern = from_can_pattern(env, procs, layout_cache, &pattern.value); match crate::pattern::check( pattern.region, @@ -1023,11 +1025,14 @@ fn store_record_destruct<'a>( is_unwrapped: true, }; - match &destruct.guard { - None => { + match &destruct.typ { + DestructType::Required => { stored.push((destruct.symbol, destruct.layout.clone(), load)); } - Some(guard_pattern) => match &guard_pattern { + DestructType::Optional(_expr) => { + todo!("TODO monomorphize optional field destructure's default expr"); + } + DestructType::Guard(guard_pattern) => match &guard_pattern { Identifier(symbol) => { stored.push((*symbol, destruct.layout.clone(), load)); } @@ -1097,7 +1102,15 @@ fn from_can_defs<'a>( let (loc_body, ret_var) = *boxed_body; - procs.insert_named(env, *symbol, ann, loc_args, loc_body, ret_var); + procs.insert_named( + env, + layout_cache, + *symbol, + ann, + loc_args, + loc_body, + ret_var, + ); continue; } @@ -1107,7 +1120,7 @@ fn from_can_defs<'a>( } // If it wasn't specifically an Identifier & Closure, proceed as normal. - let mono_pattern = from_can_pattern(env, &loc_pattern.value); + let mono_pattern = from_can_pattern(env, procs, layout_cache, &loc_pattern.value); let layout = layout_cache .from_var(env.arena, def.expr_var, env.subs, env.pointer_size) @@ -1195,7 +1208,7 @@ fn from_can_when<'a>( let loc_when_pattern = &first.patterns[0]; - let mono_pattern = from_can_pattern(env, &loc_when_pattern.value); + let mono_pattern = from_can_pattern(env, procs, layout_cache, &loc_when_pattern.value); // record pattern matches can have 1 branch and typecheck, but may still not be exhaustive let guard = if first.guard.is_some() { @@ -1259,7 +1272,7 @@ fn from_can_when<'a>( }; for loc_pattern in when_branch.patterns { - let mono_pattern = from_can_pattern(env, &loc_pattern.value); + let mono_pattern = from_can_pattern(env, procs, layout_cache, &loc_pattern.value); loc_branches.push(( Located::at(loc_pattern.region, mono_pattern.clone()), @@ -1632,7 +1645,7 @@ fn specialize<'a>( /// A pattern, including possible problems (e.g. shadowing) so that /// codegen can generate a runtime error if this pattern is reached. -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq)] pub enum Pattern<'a> { Identifier(Symbol), Underscore, @@ -1666,12 +1679,19 @@ pub enum Pattern<'a> { UnsupportedPattern(Region), } -#[derive(Clone, Debug, PartialEq, Eq, Hash)] +#[derive(Clone, Debug, PartialEq)] pub struct RecordDestruct<'a> { pub label: Lowercase, pub layout: Layout<'a>, pub symbol: Symbol, - pub guard: Option>, + pub typ: DestructType<'a>, +} + +#[derive(Clone, Debug, PartialEq)] +pub enum DestructType<'a> { + Required, + Optional(Expr<'a>), + Guard(Pattern<'a>), } #[derive(Clone, Debug, PartialEq)] @@ -1683,6 +1703,8 @@ pub struct WhenBranch<'a> { fn from_can_pattern<'a>( env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, can_pattern: &roc_can::pattern::Pattern, ) -> Pattern<'a> { use roc_can::pattern::Pattern::*; @@ -1786,7 +1808,10 @@ fn from_can_pattern<'a>( let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena); for ((_, loc_pat), layout) in arguments.iter().zip(field_layouts.iter()) { - mono_args.push((from_can_pattern(env, &loc_pat.value), layout.clone())); + mono_args.push(( + from_can_pattern(env, procs, layout_cache, &loc_pat.value), + layout.clone(), + )); } let layout = Layout::Struct(field_layouts.into_bump_slice()); @@ -1825,7 +1850,10 @@ fn from_can_pattern<'a>( // disregard the tag discriminant layout let it = argument_layouts[1..].iter(); for ((_, loc_pat), layout) in arguments.iter().zip(it) { - mono_args.push((from_can_pattern(env, &loc_pat.value), layout.clone())); + mono_args.push(( + from_can_pattern(env, procs, layout_cache, &loc_pat.value), + layout.clone(), + )); } let mut layouts: Vec<&'a [Layout<'a>]> = @@ -1876,6 +1904,8 @@ fn from_can_pattern<'a>( mono_destructs.push(from_can_record_destruct( env, + procs, + layout_cache, &destruct.value, field_layout.clone(), )); @@ -1885,7 +1915,7 @@ fn from_can_pattern<'a>( label: label.clone(), symbol: env.unique_symbol(), layout: field_layout.clone(), - guard: Some(Pattern::Underscore), + typ: DestructType::Guard(Pattern::Underscore), }); } } else { @@ -1894,7 +1924,7 @@ fn from_can_pattern<'a>( label: label.clone(), symbol: env.unique_symbol(), layout: field_layout.clone(), - guard: Some(Pattern::Underscore), + typ: DestructType::Guard(Pattern::Underscore), }); } field_layouts.push(field_layout); @@ -1910,6 +1940,8 @@ fn from_can_pattern<'a>( fn from_can_record_destruct<'a>( env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, can_rd: &roc_can::pattern::RecordDestruct, field_layout: Layout<'a>, ) -> RecordDestruct<'a> { @@ -1917,9 +1949,14 @@ fn from_can_record_destruct<'a>( label: can_rd.label.clone(), symbol: can_rd.symbol, layout: field_layout, - guard: match &can_rd.guard { - None => None, - Some((_, loc_pattern)) => Some(from_can_pattern(env, &loc_pattern.value)), + typ: match &can_rd.typ { + roc_can::pattern::DestructType::Required => DestructType::Required, + roc_can::pattern::DestructType::Optional(_, loc_expr) => { + DestructType::Optional(from_can(env, loc_expr.value.clone(), procs, layout_cache)) + } + roc_can::pattern::DestructType::Guard(_, loc_pattern) => DestructType::Guard( + from_can_pattern(env, procs, layout_cache, &loc_pattern.value), + ), }, } } diff --git a/compiler/mono/src/layout.rs b/compiler/mono/src/layout.rs index 40fef509b6..ee050bf13b 100644 --- a/compiler/mono/src/layout.rs +++ b/compiler/mono/src/layout.rs @@ -313,9 +313,10 @@ fn layout_from_flat_type<'a>( // Determine the layouts of the fields, maintaining sort order let mut layouts = Vec::with_capacity_in(sorted_fields.len(), arena); - for (_, field_var) in sorted_fields { + for (_, field) in sorted_fields { use LayoutProblem::*; + let field_var = field.into_inner(); let field_content = subs.get_without_compacting(field_var).content; match Layout::new(arena, field_content, subs, pointer_size) { @@ -372,7 +373,8 @@ pub fn sort_record_fields<'a>( // Sort the fields by label let mut sorted_fields = Vec::with_capacity_in(fields_map.len(), arena); - for (label, var) in fields_map { + for (label, field) in fields_map { + let var = field.into_inner(); let layout = Layout::from_var(arena, var, subs, pointer_size) .expect("invalid layout from var"); diff --git a/compiler/mono/src/pattern.rs b/compiler/mono/src/pattern.rs index b6de39d2a6..4a78de5260 100644 --- a/compiler/mono/src/pattern.rs +++ b/compiler/mono/src/pattern.rs @@ -1,3 +1,4 @@ +use crate::expr::DestructType; use roc_collections::all::{Index, MutMap}; use roc_module::ident::{Lowercase, TagName}; use roc_region::all::{Located, Region}; @@ -66,9 +67,9 @@ fn simplify<'a>(pattern: &crate::expr::Pattern<'a>) -> Pattern { for destruct in destructures { field_names.push(destruct.label.clone()); - match &destruct.guard { - None => patterns.push(Anything), - Some(guard) => patterns.push(simplify(guard)), + match &destruct.typ { + DestructType::Required | DestructType::Optional(_) => patterns.push(Anything), + DestructType::Guard(guard) => patterns.push(simplify(guard)), } } From 62290b5cc284eb9b709060ec366127f21b148402 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 22:13:20 -0400 Subject: [PATCH 38/68] Reporting for optional fields --- compiler/reporting/src/error/type.rs | 66 ++++++++++++++++------------ 1 file changed, 39 insertions(+), 27 deletions(-) diff --git a/compiler/reporting/src/error/type.rs b/compiler/reporting/src/error/type.rs index 727e32ede8..c4ed0bc303 100644 --- a/compiler/reporting/src/error/type.rs +++ b/compiler/reporting/src/error/type.rs @@ -4,7 +4,7 @@ use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; use roc_solve::solve; use roc_types::pretty_print::Parens; -use roc_types::types::{Category, ErrorType, PatternCategory, Reason, TypeExt}; +use roc_types::types::{Category, ErrorType, PatternCategory, Reason, RecordField, TypeExt}; use std::path::PathBuf; use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder}; @@ -779,6 +779,10 @@ fn to_expr_report<'b>( Reason::InterpolatedStringVar => { unimplemented!("string interpolation is not implemented yet") } + + Reason::RecordDefaultField(_) => { + unimplemented!("record default field is not implemented yet") + } }, } } @@ -911,6 +915,8 @@ fn add_category<'b>( alloc.text(" an uniqueness attribute of type:"), ]), Storage => alloc.concat(vec![this_is, alloc.text(" a value of type:")]), + + DefaultValue(_) => alloc.concat(vec![this_is, alloc.text(" a default field of type:")]), } } @@ -1292,7 +1298,7 @@ pub fn to_doc<'b>( .map(|(k, v)| { ( alloc.string(k.as_str().to_string()), - to_doc(alloc, Parens::Unnecessary, v), + to_doc(alloc, Parens::Unnecessary, v.into_inner()), ) }) .collect(), @@ -1570,33 +1576,39 @@ fn ext_has_fixed_fields(ext: &TypeExt) -> bool { fn diff_record<'b>( alloc: &'b RocDocAllocator<'b>, - fields1: SendMap, + fields1: SendMap>, ext1: TypeExt, - fields2: SendMap, + fields2: SendMap>, ext2: TypeExt, ) -> Diff> { - let to_overlap_docs = |(field, (t1, t2)): &(Lowercase, (ErrorType, ErrorType))| { - let diff = to_diff(alloc, Parens::Unnecessary, t1.clone(), t2.clone()); + let to_overlap_docs = + |(field, (t1, t2)): &(Lowercase, (RecordField, RecordField))| { + let diff = to_diff( + alloc, + Parens::Unnecessary, + t1.clone().into_inner(), + t2.clone().into_inner(), + ); - Diff { - left: ( - field.clone(), - alloc.string(field.as_str().to_string()), - diff.left, - ), - right: ( - field.clone(), - alloc.string(field.as_str().to_string()), - diff.right, - ), - status: diff.status, - } - }; - let to_unknown_docs = |(field, tipe): &(Lowercase, ErrorType)| { + Diff { + left: ( + field.clone(), + alloc.string(field.as_str().to_string()), + diff.left, + ), + right: ( + field.clone(), + alloc.string(field.as_str().to_string()), + diff.right, + ), + status: diff.status, + } + }; + let to_unknown_docs = |(field, tipe): &(Lowercase, RecordField)| { ( field.clone(), alloc.string(field.as_str().to_string()), - to_doc(alloc, Parens::Unnecessary, tipe.clone()), + to_doc(alloc, Parens::Unnecessary, tipe.clone().into_inner()), ) }; let shared_keys = fields1 @@ -1874,7 +1886,7 @@ mod report_text { use crate::report::{Annotation, RocDocAllocator, RocDocBuilder}; use roc_module::ident::Lowercase; use roc_types::pretty_print::Parens; - use roc_types::types::{ErrorType, TypeExt}; + use roc_types::types::{ErrorType, RecordField, TypeExt}; use ven_pretty::DocAllocator; fn with_parens<'b>( @@ -1959,16 +1971,16 @@ mod report_text { pub fn to_suggestion_record<'b>( alloc: &'b RocDocAllocator<'b>, - f: (Lowercase, ErrorType), - fs: Vec<(Lowercase, ErrorType)>, + f: (Lowercase, RecordField), + fs: Vec<(Lowercase, RecordField)>, ext: TypeExt, ) -> RocDocBuilder<'b> { use crate::error::r#type::{ext_to_doc, to_doc}; - let entry_to_doc = |(name, tipe): (Lowercase, ErrorType)| { + let entry_to_doc = |(name, tipe): (Lowercase, RecordField)| { ( alloc.string(name.as_str().to_string()), - to_doc(alloc, Parens::Unnecessary, tipe), + to_doc(alloc, Parens::Unnecessary, tipe.into_inner()), ) }; From fddc7f31db568b2493a176b44bb91965bcea63b2 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 22:17:41 -0400 Subject: [PATCH 39/68] Fix an annotation formatting regression --- compiler/fmt/src/annotation.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/fmt/src/annotation.rs b/compiler/fmt/src/annotation.rs index f7ea0a5b7d..5f7c501058 100644 --- a/compiler/fmt/src/annotation.rs +++ b/compiler/fmt/src/annotation.rs @@ -307,7 +307,7 @@ fn format_assigned_field_help<'a, T>( } buf.push_str(separator_prefix); - buf.push(':'); + buf.push_str(": "); ann.value.format(buf, indent); } OptionalValue(name, spaces, ann) => { From bcb67b0deae1824f932741962f8518c8b5548530 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 22:25:29 -0400 Subject: [PATCH 40/68] Appease clippy --- compiler/mono/src/expr.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/mono/src/expr.rs b/compiler/mono/src/expr.rs index f8313d333b..a62c9d7c9f 100644 --- a/compiler/mono/src/expr.rs +++ b/compiler/mono/src/expr.rs @@ -52,6 +52,8 @@ pub enum InProgressProc<'a> { } impl<'a> Procs<'a> { + // TODO trim down these arguments! + #[allow(clippy::too_many_arguments)] pub fn insert_named( &mut self, env: &mut Env<'a, '_>, From ece8480195bd389db57e50452c1ca95862c1d5bd Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 14 Jul 2020 08:20:04 -0400 Subject: [PATCH 41/68] Drop obsolete Arr.roc --- compiler/builtins/docs/Arr.roc | 264 --------------------------------- 1 file changed, 264 deletions(-) delete mode 100644 compiler/builtins/docs/Arr.roc diff --git a/compiler/builtins/docs/Arr.roc b/compiler/builtins/docs/Arr.roc deleted file mode 100644 index 5493c9bad8..0000000000 --- a/compiler/builtins/docs/Arr.roc +++ /dev/null @@ -1,264 +0,0 @@ -interface List - exposes [ List, map, fold ] - imports [] - -## Types - -## A sequential list of values. -## -## >>> [ 1, 2, 3 ] # a list of numbers -## -## >>> [ "a", "b", "c" ] # a list of strings -## -## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of floats -## -## The list [ 1, "a" ] gives an error, because each element in a list must have -## the same type. If you want to put a mix of #Int and #Str values into a list, try this: -## -## ``` -## mixedList : List [ IntElem Int, StrElem Str ]* -## mixedList = [ IntElem 1, IntElem 2, StrElem "a", StrElem "b" ] -## ``` -## -## The maximum size of a #List is limited by the amount of heap memory available -## to the current process. If there is not enough memory available, attempting to -## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html) -## is normally enabled, not having enough memory could result in the list appearing -## to be created just fine, but then crashing later.) -## -## > The theoretical maximum length for a list created in Roc is -## > #Int.highestUlen divided by 2. Attempting to create a list bigger than that -## > in Roc code will always fail, although in practice it is likely to fail -## > at much smaller lengths due to insufficient memory being available. -## -## ## Performance notes -## -## Under the hood, a list is a record containing a `len : Ulen` field as well -## as a pointer to a flat list of bytes. -## -## This is not a [persistent data structure](https://en.wikipedia.org/wiki/Persistent_data_structure), -## so copying it is not cheap! The reason #List is designed this way is because: -## -## * Copying small lists is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures are usually thin wrappers around flat lists anyway. They don't start conferring copying advantages until crossing a certain minimum size threshold. -## Many list operations are no faster with persistent data structures. For example, even if it were a persistent data structure, #List.map, #List.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch. -## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, #List.map, #List.keepIf, and #List.set can all be optimized to perform in-place mutations. -## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way #List worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using #List under the hood! -List elem : @List elem - -## Initialize - -single : elem -> List elem - -empty : List * - -repeat : elem, Ulen -> List elem - -range : Int a, Int a -> List (Int a) - -## TODO I don't think we should have this after all. *Maybe* have an Ok.toList instead? -## -## When given an #Err, returns #[], and when given #Ok, returns a list containing -## only that value. -## -## >>> List.fromResult (Ok 5) -## -## >>> List.fromResult (Err "the Err's contents get discarded") -## -## This is useful when using `List.joinMap` with a function ## that returns a #Result. -## -## > (List.joinMap [ "cat", "", "bat", "" ] Str.first) |> List.map List.fromResult -fromResult : Result elem * -> List elem - -## Transform - -reverse : List elem -> List elem - -sort : List elem, Sorter elem -> List elem - -## Convert each element in the list to something new, by calling a conversion -## function on each of them. Then return a new list of the converted values. -## -## > List.map [ 1, 2, 3 ] (\num -> num + 1) -## -## > List.map [ "", "a", "bc" ] Str.isEmpty -## -## `map` functions like this are common in Roc, and they all work similarly. -## See for example #Result.map, #Set.map, and #Map.map. -map : List before, (before -> after) -> List after - -## This works the same way as #List.map, except it also passes the index -## of the element to the conversion function. -indexedMap : List before, (before, Int -> after) -> List after - -## Add a single element to the end of a list. -## -## >>> List.append [ 1, 2, 3 ] 4 -## -## >>> [ 0, 1, 2 ] -## >>> |> List.append 3 -append : List elem, elem -> List elem - -## Add a single element to the beginning of a list. -## -## >>> List.prepend [ 1, 2, 3 ] 0 -## -## >>> [ 2, 3, 4 ] -## >>> |> List.prepend 1 -prepend : List elem, elem -> List elem - -## Put two lists together. -## -## >>> List.concat [ 1, 2, 3 ] [ 4, 5 ] -## -## >>> [ 0, 1, 2 ] -## >>> |> List.concat [ 3, 4 ] -concat : List elem, List elem -> List elem - -## Join the given lists together into one list. -## -## >>> List.join [ [ 1, 2, 3 ], [ 4, 5 ], [], [ 6, 7 ] ] -## -## >>> List.join [ [], [] ] -## -## >>> List.join [] -join : List (List elem) -> List elem - -joinMap : List before, (before -> List after) -> List after - -## Like #List.join, but only keeps elements tagged with `Ok`. Elements -## tagged with `Err` are dropped. -## -## This can be useful after using an operation that returns a #Result -## on each element of a list, for example #List.first: -## -## >>> [ [ 1, 2, 3 ], [], [], [ 4, 5 ] ] -## >>> |> List.map List.first -## >>> |> List.joinOks -joinOks : List (Result elem *) -> List elem - -## Iterates over the shortest of the given lists and returns a list of `Pair` -## tags, each wrapping one of the elements in that list, along with the elements -## in the same position in # the other lists. -## -## >>> List.zip [ "a1", "b1" "c1" ] [ "a2", "b2" ] [ "a3", "b3", "c3" ] -## -## Accepts up to 8 lists. -## -## > For a generalized version that returns whatever you like, instead of a `Pair`, -## > see `zipMap`. -zip : - List a, List b, -> List [ Pair a b ]* - List a, List b, List c, -> List [ Pair a b c ]* - List a, List b, List c, List d -> List [ Pair a b c d ]* - -## Like `zip` but you can specify what to do with each element. -## -## More specifically, [repeat what zip's docs say here] -## -## >>> List.zipMap [ 1, 2, 3 ] [ 0, 5, 4 ] [ 2, 1 ] \num1 num2 num3 -> num1 + num2 - num3 -## -## Accepts up to 8 lists. -zipMap : - List a, List b, (a, b) -> List c | - List a, List b, List c, (a, b, c) -> List d | - List a, List b, List c, List d, (a, b, c, d) -> List e - - -## Filter - -## Run the given function on each element of a list, and return all the -## elements for which the function returned `True`. -## -## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2) -## -## ## Performance Notes -## -## #List.keepIf always returns a list that takes up exactly the same amount -## of memory as the original, even if its length decreases. This is becase it -## can't know in advance exactly how much space it will need, and if it guesses a -## length that's too low, it would have to re-allocate. -## -## (If you want to do an operation like this which reduces the memory footprint -## of the resulting list, you can do two passes over the lis with #List.fold - one -## to calculate the precise new size, and another to populate the new list.) -## -## If given a unique list, #List.keepIf will mutate it in place to assemble the appropriate list. -## If that happens, this function will not allocate any new memory on the heap. -## If all elements in the list end up being kept, Roc will return the original -## list unaltered. -## -keepIf : List elem, (elem -> [True, False]) -> List elem - -## Run the given function on each element of a list, and return all the -## elements for which the function returned `False`. -## -## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2) -## -## ## Performance Notes -## -## #List.dropIf has the same performance characteristics as #List.keepIf. -## See its documentation for details on those characteristics! -dropIf : List elem, (elem -> [True, False]) -> List elem - -## Takes the requested number of elements from the front of a list -## and returns them. -## -## >>> take 5 [ 1, 2, 3, 4, 5, 6, 7, 8 ] -## -## If there are fewer elements in the list than the requeted number, -## returns the entire list. -## -## >>> take 5 [ 1, 2 ] -take : List elem, Int -> List elem - -## Drops the requested number of elements from the front of a list -## and returns the rest. -## -## >>> drop 5 [ 1, 2, 3, 4, 5, 6, 7, 8 ] -## -## If there are fewer elements in the list than the requested number to -## be dropped, returns an empty list. -## -## >>> drop 5 [ 1, 2 ] -drop : List elem, Int -> List elem - -## Access - -first : List elem -> [Ok elem, ListWasEmpty]* - -last : List elem -> [Ok elem, ListWasEmpty]* - -get : List elem, Ulen -> [Ok elem, OutOfBounds]* - -max : List (Num a) -> [Ok (Num a), ListWasEmpty]* - -min : List (Num a) -> [Ok (Num a), ListWasEmpty]* - -## Modify - -set : List elem, Ulen, elem -> List elem - -## Deconstruct - -split : List elem, Int -> { before: List elem, remainder: List elem } - -walk : List elem, { start : state, step : (state, elem -> state) } -> state - -walkBackwards : List elem, { start : state, step : (state, elem -> state) } -> state - -## Check - -## Returns the length of the list - the number of elements it contains. -## -## One #List can store up to 2,147,483,648 elements (just over 2 billion), which -## is exactly equal to the highest valid #I32 value. This means the #U32 this function -## returns can always be safely converted to an #I32 without losing any data. -len : List * -> Ulen - -isEmpty : List * -> Bool - -contains : List elem, elem -> Bool - -all : List elem, (elem -> Bool) -> Bool - -any : List elem, (elem -> Bool) -> Bool From e8a50a0e4c55bb50d95069d912eeaaa31c0a1933 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 14 Jul 2020 08:20:36 -0400 Subject: [PATCH 42/68] Update some Num docs --- compiler/builtins/docs/Num.roc | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/compiler/builtins/docs/Num.roc b/compiler/builtins/docs/Num.roc index 84dfc84c19..3c9816aa12 100644 --- a/compiler/builtins/docs/Num.roc +++ b/compiler/builtins/docs/Num.roc @@ -475,6 +475,23 @@ ceil : Float * -> Int * floor : Float * -> Int * trunc : Float * -> Int * +## Convert an #Int to a #Len. If the given number doesn't fit in #Len, it will be truncated. +## Since #Len has a different maximum number depending on the system you're building +## for, this may give a different answer on different systems. +## +## For example, on a 32-bit sytem, #Num.maxLen will return the same answer as +## #Num.maxU32. This means that calling `Num.toLen 9_000_000_000` on a 32-bit +## system will return #Num.maxU32 instead of 9 billion, because 9 billion is +## higher than #Num.maxU32 and will not fit in a #Len on a 32-bit system. +## +## However, calling `Num.toLen 9_000_000_000` on a 64-bit system will return +## the #Len value of 9_000_000_000. This is because on a 64-bit system, #Len can +## hold up to #Num.maxU64, and 9_000_000_000 is lower than #Num.maxU64. +## +## To convert a #Float to a #Len, first call either #Num.round, #Num.ceil, or #Num.floor +## on it, then call this on the resulting #Int. +toLen : Int * -> Len + ## Convert an #Int to an #I8. If the given number doesn't fit in #I8, it will be truncated. ## ## To convert a #Float to an #I8, first call either #Num.round, #Num.ceil, or #Num.floor @@ -484,6 +501,7 @@ toI16 : Int * -> I16 toI32 : Int * -> I32 toI64 : Int * -> I64 toI128 : Int * -> I128 + ## Convert an #Int to an #U8. If the given number doesn't fit in #U8, it will be truncated. ## Crashes if the given number is negative. toU8 : Int * -> U8 From 190b9790ca0d24055241a8afe0af452eb42df9ba Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 14 Jul 2020 08:20:26 -0400 Subject: [PATCH 43/68] Add initial List.roc --- compiler/builtins/docs/List.roc | 444 ++++++++++++++++++++++++++++++++ 1 file changed, 444 insertions(+) create mode 100644 compiler/builtins/docs/List.roc diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc new file mode 100644 index 0000000000..cc0ed9d5d9 --- /dev/null +++ b/compiler/builtins/docs/List.roc @@ -0,0 +1,444 @@ +interface List + exposes [ List, map, fold ] + imports [] + +## Types + +## A sequential list of values. +## +## >>> [ 1, 2, 3 ] # a list of numbers +## +## >>> [ "a", "b", "c" ] # a list of strings +## +## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of floats +## +## The list [ 1, "a" ] gives an error, because each element in a list must have +## the same type. If you want to put a mix of #Int and #Str values into a list, try this: +## +## ``` +## mixedList : List [ IntElem Int, StrElem Str ]* +## mixedList = [ IntElem 1, IntElem 2, StrElem "a", StrElem "b" ] +## ``` +## +## The maximum size of a #List is limited by the amount of heap memory available +## to the current process. If there is not enough memory available, attempting to +## create the list could crash. (On Linux, where [overcommit](https://www.etalabs.net/overcommit.html) +## is normally enabled, not having enough memory could result in the list appearing +## to be created just fine, but then crashing later.) +## +## > The theoretical maximum length for a list created in Roc is +## > #Int.highestLen divided by 2. Attempting to create a list bigger than that +## > in Roc code will always fail, although in practice it is likely to fail +## > at much smaller lengths due to insufficient memory being available. +## +## ## Performance Details +## +## Under the hood, a list is a record containing a `len : Len` field as well +## as a pointer to a flat list of bytes. +## +## This is not a [persistent data structure](https://en.wikipedia.org/wiki/Persistent_data_structure), +## so copying it is not cheap! The reason #List is designed this way is because: +## +## * Copying small lists is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures are usually thin wrappers around flat lists anyway. They don't start conferring copying advantages until crossing a certain minimum size threshold. +## Many list operations are no faster with persistent data structures. For example, even if it were a persistent data structure, #List.map, #List.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch. +## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, #List.map, #List.keepIf, and #List.set can all be optimized to perform in-place mutations. +## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way #List worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using #List under the hood! +List elem : @List elem + +## Initialize + +## A list with a single element in it. +## +## This is useful in pipelines, like so: +## +## websites = +## Str.concat domain ".com" +## |> List.single +## +single : elem -> List elem + +## An empty list. +empty : List * + +## Returns a list with the given length, where every element is the given value. +## +## +repeat : elem, Len -> List elem + +## Returns a list of all the integers between one and another, +## including both of the given numbers. +## +## >>> List.range 2 8 +range : Int a, Int a -> List (Int a) + +## Transform + +## Returns the list with its elements reversed. +## +## >>> List.reverse [ 1, 2, 3 ] +reverse : List elem -> List elem + +## Sorts a list using a function which specifies how two elements are ordered. +## +## +sort : List elem, (elem, elem -> [ Lt, Eq, Gt ]) -> List elem + +## Convert each element in the list to something new, by calling a conversion +## function on each of them. Then return a new list of the converted values. +## +## > List.map [ 1, 2, 3 ] (\num -> num + 1) +## +## > List.map [ "", "a", "bc" ] Str.isEmpty +## +## `map` functions like this are common in Roc, and they all work similarly. +## See for example #Result.map, #Set.map, and #Map.map. +map : List before, (before -> after) -> List after + +## This works the same way as #List.map, except it also passes the index +## of the element to the conversion function. +indexedMap : List before, (before, Int -> after) -> List after + +## Add a single element to the end of a list. +## +## >>> List.append [ 1, 2, 3 ] 4 +## +## >>> [ 0, 1, 2 ] +## >>> |> List.append 3 +append : List elem, elem -> List elem + +## Add a single element to the beginning of a list. +## +## >>> List.prepend [ 1, 2, 3 ] 0 +## +## >>> [ 2, 3, 4 ] +## >>> |> List.prepend 1 +prepend : List elem, elem -> List elem + +## Put two lists together. +## +## >>> List.concat [ 1, 2, 3 ] [ 4, 5 ] +## +## >>> [ 0, 1, 2 ] +## >>> |> List.concat [ 3, 4 ] +concat : List elem, List elem -> List elem + +## Join the given lists together into one list. +## +## >>> List.join [ [ 1, 2, 3 ], [ 4, 5 ], [], [ 6, 7 ] ] +## +## >>> List.join [ [], [] ] +## +## >>> List.join [] +join : List (List elem) -> List elem + +joinMap : List before, (before -> List after) -> List after + +## Like #List.join, but only keeps elements tagged with `Ok`. Elements +## tagged with `Err` are dropped. +## +## This can be useful after using an operation that returns a #Result +## on each element of a list, for example #List.first: +## +## >>> [ [ 1, 2, 3 ], [], [], [ 4, 5 ] ] +## >>> |> List.map List.first +## >>> |> List.joinOks +joinOks : List (Result elem *) -> List elem + +## Iterates over the shortest of the given lists and returns a list of `Pair` +## tags, each wrapping one of the elements in that list, along with the elements +## in the same position in # the other lists. +## +## >>> List.zip [ "a1", "b1" "c1" ] [ "a2", "b2" ] [ "a3", "b3", "c3" ] +## +## Accepts up to 8 lists. +## +## > For a generalized version that returns whatever you like, instead of a `Pair`, +## > see `zipMap`. +zip : + List a, List b, -> List [ Pair a b ]* + List a, List b, List c, -> List [ Pair a b c ]* + List a, List b, List c, List d -> List [ Pair a b c d ]* + +## Like `zip` but you can specify what to do with each element. +## +## More specifically, [repeat what zip's docs say here] +## +## >>> List.zipMap [ 1, 2, 3 ] [ 0, 5, 4 ] [ 2, 1 ] \num1 num2 num3 -> num1 + num2 - num3 +## +## Accepts up to 8 lists. +zipMap : + List a, List b, (a, b) -> List c | + List a, List b, List c, (a, b, c) -> List d | + List a, List b, List c, List d, (a, b, c, d) -> List e + + +## Filter + +## Run the given function on each element of a list, and return all the +## elements for which the function returned `True`. +## +## >>> List.keepIf [ 1, 2, 3, 4 ] (\num -> num > 2) +## +## ## Performance Details +## +## #List.keepIf always returns a list that takes up exactly the same amount +## of memory as the original, even if its length decreases. This is becase it +## can't know in advance exactly how much space it will need, and if it guesses a +## length that's too low, it would have to re-allocate. +## +## (If you want to do an operation like this which reduces the memory footprint +## of the resulting list, you can do two passes over the lis with #List.fold - one +## to calculate the precise new size, and another to populate the new list.) +## +## If given a unique list, #List.keepIf will mutate it in place to assemble the appropriate list. +## If that happens, this function will not allocate any new memory on the heap. +## If all elements in the list end up being kept, Roc will return the original +## list unaltered. +## +keepIf : List elem, (elem -> [True, False]) -> List elem + +## Run the given function on each element of a list, and return all the +## elements for which the function returned `False`. +## +## >>> List.dropIf [ 1, 2, 3, 4 ] (\num -> num > 2) +## +## ## Performance Details +## +## #List.dropIf has the same performance characteristics as #List.keepIf. +## See its documentation for details on those characteristics! +dropIf : List elem, (elem -> [True, False]) -> List elem + +## Takes the requested number of elements from the front of a list +## and returns them. +## +## >>> take 5 [ 1, 2, 3, 4, 5, 6, 7, 8 ] +## +## If there are fewer elements in the list than the requeted number, +## returns the entire list. +## +## >>> take 5 [ 1, 2 ] +take : List elem, Int -> List elem + +## Access + +## Returns the first element in the list, or `ListWasEmpty` if the list was empty. +first : List elem -> Result elem [ ListWasEmpty ]* + +## Returns the last element in the list, or `ListWasEmpty` if the list was empty. +last : List elem -> Result elem [ ListWasEmpty ]* + +## This takes a #Len because the maximum length of a #List is a #Len value, +## so #Len lets you specify any position up to the maximum length of +## the list. +get : List elem, Len -> Result elem [ OutOfBounds ]* + +max : List (Num a) -> Result (Num a) [ ListWasEmpty ]* + +min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* + +## Modify + +## This takes a #Len because the maximum length of a #List is a #Len value, +## so #Len lets you specify any position up to the maximum length of +## the list. +set : List elem, Len, elem -> List elem + +## Add a new element to the end of a list. +## +## Returns a new list with the given element as its last element. +## +## ## Performance Details +## +## When given a Unique list, this adds the new element in-place if possible. +## This is only possible if the list has enough capacity. Otherwise, it will +## have to *clone and grow*. See the section on [capacity](#capacity) in this +## module's documentation. +append : List elem, elem -> List elem + +## Add a new element to the beginning of a list. +## +## Returns a new list with the given element as its first element. +## +## ## Performance Details +## +## This always clones the entire list, even when given a Unique list. That means +## it runs about as fast as #List.addLast when both are given a Shared list. +## +## If you have a Unique list instead, #List.append will run much faster than +## #List.prepend except in the specific case where the list has no excess capacity, +## and needs to *clone and grow*. In that uncommon case, both #List.append and +## #List.prepend will run at about the same speed—since #List.prepend always +## has to clone and grow. +## +## | Unique list | Shared list | +##---------+--------------------------------+----------------+ +## append | in-place given enough capacity | clone and grow | +## prepend | clone and grow | clone and grow | +prepend : List elem, elem -> List elem + +## Remove the last element from the list. +## +## Returns both the removed element as well as the new list (with the removed +## element missing), or `Err ListWasEmpty` if the list was empty. +## +## Here's one way you can use this: +## +## when List.pop list is +## Ok { others, last } -> ... +## Err ListWasEmpty -> ... +## +## ## Performance Details +## +## Calling #List.pop on a Unique list runs extremely fast. It's essentially +## the same as a #List.last except it also returns the #List it was given, +## with its length decreased by 1. +## +## In contrast, calling #List.pop on a Shared list creates a new list, then +## copies over every element in the original list except the last one. This +## takes much longer. +dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpty ]* + +## +## Here's one way you can use this: +## +## when List.pop list is +## Ok { others, last } -> ... +## Err ListWasEmpty -> ... +## +## ## Performance Details +## +## When calling either #List.dropFirst or #List.dropLast on a Unique list, #List.dropLast +## runs *much* faster. This is because for #List.dropLast, removing the last element +## in-place is as easy as reducing the length of the list by 1. In contrast, +## removing the first element from the list involves copying every other element +## in the list into the position before it - which is massively more costly. +## +## In the case of a Shared list, +## +## | Unique list | Shared list | +##-----------+----------------------------------+---------------------------------+ +## dropFirst | #List.last + length change | #List.last + clone rest of list | +## dropLast | #List.last + clone rest of list | #List.last + clone rest of list | +dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmpty ]* + +## Drops the given number of elements from the end of the list. +## +## Returns a new list without the dropped elements. +## +## To remove elements from a list while also returning a list of the removed +## elements, use #List.split. +## +## To remove elements from the beginning of the list, use #List.dropFromFront. +## +## ## Performance Details +## +## When given a Unique list, this runs extremely fast. It subtracts the given +## number from the list's length (down to a minimum of 0) in-place, and that's it. +## +## In fact, `List.drop 1 list` runs faster than `List.dropLast list` when given +## a Unique list, because #List.dropLast returns the element it dropped - +## which introduces a conditional bounds check as well as a memory load. +drop : List elem, Len -> List elem + +## Drops the given number of elements from the front of the list. +## +## Returns a new list without the dropped elements. +## +## To remove elements from a list while also returning a list of the removed +## elements, use #List.split. +## +## ## Performance Details +## +## When given a Unique list, this runs extremely fast. It subtracts the given +## number from the list's length (down to a minimum of 0) in-place, and that's it. +## +## In fact, `List.drop 1 list` runs faster than `List.dropLast list` when given +## a Unique list, because #List.dropLast returns the element it dropped - +## which introduces a conditional bounds check as well as a memory load. +dropFromFront : List elem, Len -> List elem + +## Deconstruct + +## Splits the list into two lists, around the given index. +## +## The returned lists are labeled `before` and `others`. The `before` list will +## contain all the elements whose index in the original list was **less than** +## than the given index, # and the `others` list will be all the others. (This +## means if you give an index of 0, the `before` list will be empty and the +## `others` list will have the same elements as the original list.) +split : List elem, Len -> { before: List elem, others: List elem } + +## Build a value using each element in the list. +## +## Starting with a given `state` value, this walks through each element in the +## list from first to last, running a given `step` function on that element +## which updates the `state`. It returns the final `state` at the end. +## +## You can use it in a pipeline: +## +## [ 2, 4, 8 ] +## |> List.walk { start: 0, step: Num.add } +## +## This returns 14 because: +## * `state` starts at 0 (because of `start: 0`) +## * Each `step` runs `Num.add state elem`, and the return value becomes the new `state`. +## +## Here is a table of how `state` changes as #List.walk walks over the elements +## `[ 2, 4, 8 ]` using #Num.add as its `step` function to determine the next `state`. +## +## `state` | `elem` | `step state elem` (`Num.add state elem`) +## --------+--------+----------------------------------------- +## 0 | | +## 0 | 2 | 2 +## 2 | 4 | 6 +## 6 | 8 | 14 +## +## So `state` goes through these changes: +## 1. `0` (because of `start: 0`) +## 2. `1` (because of `Num.add state elem` with `state` = 0 and `elem` = 1 +## +## [ 1, 2, 3 ] +## |> List.walk { start: 0, step: Num.sub } +## +## This returns -6 because +## +## Note that in other languages, `walk` is sometimes called `reduce`, +## `fold`, `foldLeft`, or `foldl`. +walk : List elem, { start : state, step : (state, elem -> state) } -> state + +## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`, +## `fold`, `foldRight`, or `foldr`. +walkBackwards : List elem, { start : state, step : (state, elem -> state ]) } -> state + +## Same as #List.walk, except you can stop walking early. +## +## ## Performance Details +## +## Compared to #List.walk, this can potentially visit fewer elements (which can +## improve performance) at the cost of making each step take longer. +## However, the added cost to each step is extremely small, and can easily +## be outweighed if it results in skipping even a small number of elements. +## +## As such, it is typically better for performance to use this over #List.walk +## if returning `Done` earlier than the last element is expected to be common. +walkUntil : List elem, { start : state, step : (state, elem -> [ Continue state, Done state ]) } -> state + +# Same as #List.walkBackwards, except you can stop walking early. +walkBackwardsUntil : List elem, { start : state, step : (state, elem -> [ Continue state, Done state ]) } -> state + +## Check + +## Returns the length of the list - the number of elements it contains. +## +## One #List can store up to 2,147,483,648 elements (just over 2 billion), which +## is exactly equal to the highest valid #I32 value. This means the #U32 this function +## returns can always be safely converted to an #I32 without losing any data. +len : List * -> Len + +isEmpty : List * -> Bool + +contains : List elem, elem -> Bool + +all : List elem, (elem -> Bool) -> Bool + +any : List elem, (elem -> Bool) -> Bool From 12fb1ed7e98b8146b5362ad7baa986b8f96c67a1 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 11:04:57 -0400 Subject: [PATCH 44/68] Update List docs some more --- compiler/builtins/docs/List.roc | 135 +++++++++++++++++++++++++++++--- 1 file changed, 126 insertions(+), 9 deletions(-) diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index cc0ed9d5d9..fc2a4b504e 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -5,8 +5,7 @@ interface List ## Types ## A sequential list of values. -## -## >>> [ 1, 2, 3 ] # a list of numbers +## # >>> [ 1, 2, 3 ] # a list of numbers ## ## >>> [ "a", "b", "c" ] # a list of strings ## @@ -34,13 +33,119 @@ interface List ## ## Performance Details ## ## Under the hood, a list is a record containing a `len : Len` field as well -## as a pointer to a flat list of bytes. +## as a pointer to a reference count and a flat array of bytes. Unique lists +## store a capacity #Len instead of a reference count. ## -## This is not a [persistent data structure](https://en.wikipedia.org/wiki/Persistent_data_structure), -## so copying it is not cheap! The reason #List is designed this way is because: +## ## Shared Lists ## -## * Copying small lists is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures are usually thin wrappers around flat lists anyway. They don't start conferring copying advantages until crossing a certain minimum size threshold. -## Many list operations are no faster with persistent data structures. For example, even if it were a persistent data structure, #List.map, #List.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch. +## Shared lists are [reference counted](https://en.wikipedia.org/wiki/Reference_counting). +## +## Each time a given list gets referenced, its reference count ("refcount" for short) +## gets incremented. Each time a list goes out of scope, its refcount count gets +## decremented. Once a refcount, has been decremented more times than it has been +## incremented, we know nothing is referencing it anymore, and the list's memory +## will be immediately freed. +## +## Let's look at an example. +## +## ratings = [ 5, 4, 3 ] +## +## { foo: ratings, bar: ratings } +## +## The first line binds the name `ratings` to the list `[ 5, 4, 3 ]`. The list +## begins with a refcount of 1, because so far only `ratings` is referencing it. +## +## The second line alters this refcount. `{ foo: ratings` references +## the `ratings` list, which will result in its refcount getting incremented +## from 0 to 1. Similarly, `bar: ratings }` also references the `ratings` list, +## which will result in its refcount getting incremented from 1 to 2. +## +## Let's turn this example into a function. +## +## getRatings = \first -> +## ratings = [ first, 4, 3 ] +## +## { foo: ratings, bar: ratings } +## +## getRatings 5 +## +## At the end of the `getRatings` function, when the record gets returned, +## the original `ratings =` binding has gone out of scope and is no longer +## accessible. (Trying to reference `ratings` outside the scope of the +## `getRatings` function would be an error!) +## +## Since `ratings` represented a way to reference the list, and that way is no +## longer accessible, the list's refcount gets decremented when `ratings` goes +## out of scope. It will decrease from 2 back down to 1. +## +## Putting these together, when we call `getRatings 5`, what we get back is +## a record with two fields, `foo`, and `bar`, each of which refers to the same +## list, and that list has a refcount of 1. +## +## Let's change the last line to be `(getRatings 5).bar` instead of `getRatings 5`: +## +## getRatings = \first -> +## ratings = [ first, 4, 3 ] +## +## { foo: ratings, bar: ratings } +## +## (getRatings 5).bar +## +## Now, when this expression returns, only the `bar` field of the record will +## be returned. This will mean that the `foo` field becomes inaccessible, causing +## the list's refcount to get decremented from 2 to 1. At this point, the list is back +## where it started: there is only 1 reference to it. +## +## Finally let's suppose the final line were changed to this: +## +## List.first (getRatings 5).bar +## +## This call to #List.first means that even the list in the `bar` field has become +## inaccessible. As such, this line will cause the list's refcount to get +## decremented all the way to 0. At that point, nothing is referencing the list +## anymore, and its memory will get freed. +## +## Things are different if this is a list of lists instead of a list of numbers. +## Let's look at a simpler example using #List.first - first with a list of numbers, +## and then with a list of lists, to see how they differ. +## +## Here's the example using a list of numbers. +## +## nums = [ 1, 2, 3, 4, 5, 6, 7 ] +## +## first = List.first nums +## last = List.last nums +## +## first +## +## It makes a list, calls #List.first and #List.last on it, and then returns `first`. +## +## Here's the equivalent code with a list of lists: +## +## lists = [ [ 1 ], [ 2, 3 ], [], [ 4, 5, 6, 7 ] ] +## +## first = List.first lists +## last = List.last lists +## +## first +## +## TODO explain how in the former example, when we go to free `nums` at the end, +## we can free it immediately because there are no other refcounts. However, +## in the case of `lists`, we have to iterate through the list and decrement +## the refcounts of each of its contained lists - because they, too, have +## refcounts! Importantly, beacuse the first element had its refcount incremented +## because the function returned `first`, that element will actually end up +## *not* getting freed at the end - but all the others will be. +## +## In the `lists` example, `lists = [ ... ]` also creates a list with an initial +## refcount of 1. Separately, it also creates several other lists - each with +## their own refcounts - to go inside that list. (The empty list at the end +## does not use heap memory, and thus has no refcount.) +## +## At the end, we once again call #List.first on the list, but this time +## +## * Copying small lists (64 elements or fewer) is typically slightly faster than copying small persistent data structures. This is because, at small sizes, persistent data structures tend to be thin wrappers around flat arrays anyway. They don't have any copying advantage until crossing a certain minimum size threshold. +## * Even when copying is faster, other list operations may still be slightly slower with persistent data structures. For example, even if it were a persistent data structure, #List.map, #List.fold, and #List.keepIf would all need to traverse every element in the list and build up the result from scratch. These operations are all ## * Roc's compiler optimizes many list operations into in-place mutations behind the scenes, depending on how the list is being used. For example, #List.map, #List.keepIf, and #List.set can all be optimized to perform in-place mutations. ## * If possible, it is usually best for performance to use large lists in a way where the optimizer can turn them into in-place mutations. If this is not possible, a persistent data structure might be faster - but this is a rare enough scenario that it would not be good for the average Roc program's performance if this were the way #List worked by default. Instead, you can look outside Roc's standard modules for an implementation of a persistent data structure - likely built using #List under the hood! List elem : @List elem @@ -94,9 +199,21 @@ sort : List elem, (elem, elem -> [ Lt, Eq, Gt ]) -> List elem ## See for example #Result.map, #Set.map, and #Map.map. map : List before, (before -> after) -> List after -## This works the same way as #List.map, except it also passes the index +## This works like #List.map, except it also passes the index ## of the element to the conversion function. -indexedMap : List before, (before, Int -> after) -> List after +mapWithIndex : List before, (before, Int -> after) -> List after + +## This works like #List.map, except the given function can return `Drop` to +## drop the transformed element - or wrap it in `Keep` to keep it. +## +mapOrDrop : List before, (before -> [ Keep after, Drop ]) -> List after + +## This works like #List.map, except at any time you can return `Abort` to +## cancel the operation - or wrap the element in `Continue` to continue. +mapOrCancel : List before, (before -> [ Continue after, Cancel ]) -> Result (List after) MapCanceled + +## If all the elements in the list are #Ok, +checkOk : List (Result ok err) -> Result (List ok) err ## Add a single element to the end of a list. ## From 74b8622bf9cd242187618b513b445350f75417af Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 11:05:29 -0400 Subject: [PATCH 45/68] Use character codes, not keycodes, for backspace --- editor/src/lib.rs | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 62c1fae962..2868a32112 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -131,10 +131,8 @@ fn run_event_loop() -> Result<(), Box> { match ch { '\u{8}' | '\u{7f}' => { // In Linux, we get a '\u{8}' when you press backspace, - // but in macOS we get '\u{7f}'. In both, we - // get a Back keydown event. Therefore, we use the - // Back keydown event and ignore these, resulting - // in a system that works in both Linux and macOS. + // but in macOS we get '\u{7f}'. + text_state.pop(); } '\u{e000}'..='\u{f8ff}' | '\u{f0000}'..='\u{ffffd}' @@ -240,12 +238,6 @@ fn handle_keydown( } match virtual_keycode { - Back => { - // Backspace deletes a character. - // In Linux, we get a Unicode character for backspace events - // (which is handled elsewhere), but on macOS we only get one of these. - text_state.pop(); - } Copy => { todo!("copy"); } From 743768d82a2df184c162a5709707ed28141f5697 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Sat, 18 Jul 2020 11:11:21 -0400 Subject: [PATCH 46/68] Change the type of List.mapOrCancel --- compiler/builtins/docs/List.roc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index fc2a4b504e..b52e728f32 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -208,9 +208,9 @@ mapWithIndex : List before, (before, Int -> after) -> List after ## mapOrDrop : List before, (before -> [ Keep after, Drop ]) -> List after -## This works like #List.map, except at any time you can return `Abort` to -## cancel the operation - or wrap the element in `Continue` to continue. -mapOrCancel : List before, (before -> [ Continue after, Cancel ]) -> Result (List after) MapCanceled +## This works like #List.map, except at any time you can return `Err` to +## cancel the operation and return an error, or `Ok` to continue. +mapOrCancel : List before, (before -> Result after err) -> Result (List after) err ## If all the elements in the list are #Ok, checkOk : List (Result ok err) -> Result (List ok) err From 6d40de7430d26dbb10873d433bafc86de4270504 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 20 Jul 2020 21:39:43 -0400 Subject: [PATCH 47/68] Revise List docs some more --- compiler/builtins/docs/List.roc | 159 ++++++++++++++++++-------------- 1 file changed, 92 insertions(+), 67 deletions(-) diff --git a/compiler/builtins/docs/List.roc b/compiler/builtins/docs/List.roc index b52e728f32..ae7652be0a 100644 --- a/compiler/builtins/docs/List.roc +++ b/compiler/builtins/docs/List.roc @@ -5,9 +5,7 @@ interface List ## Types ## A sequential list of values. -## # >>> [ 1, 2, 3 ] # a list of numbers -## -## >>> [ "a", "b", "c" ] # a list of strings +## # >>> [ 1, 2, 3 ] # a list of numbers # # >>> [ "a", "b", "c" ] # a list of strings ## ## >>> [ [ 1.1 ], [], [ 2.2, 3.3 ] ] # a list of lists of floats ## @@ -203,17 +201,23 @@ map : List before, (before -> after) -> List after ## of the element to the conversion function. mapWithIndex : List before, (before, Int -> after) -> List after -## This works like #List.map, except the given function can return `Drop` to -## drop the transformed element - or wrap it in `Keep` to keep it. -## -mapOrDrop : List before, (before -> [ Keep after, Drop ]) -> List after - ## This works like #List.map, except at any time you can return `Err` to -## cancel the operation and return an error, or `Ok` to continue. +## cancel the entire operation immediately, and return that #Err. mapOrCancel : List before, (before -> Result after err) -> Result (List after) err -## If all the elements in the list are #Ok, -checkOk : List (Result ok err) -> Result (List ok) err +## This works like #List.map, except only the transformed values that are +## wrapped in `Ok` are kept. Any that are wrapped in `Err` are dropped. +## +## >>> List.mapOks [ [ "a", "b" ], [], [], [ "c", "d", "e" ] ] List.last +## +## >>> fn = \str -> if Str.isEmpty str then Err StrWasEmpty else Ok (Str.len str) +## >>> +## >>> List.mapOks [ "", "a", "bc", "", "d", "ef", "" ] +mapOks : List before, (before -> Result after *) -> List after + +## If all the elements in the list are #Ok, return a new list containing the +## contents of those #Ok tags. If any elements are #Err, return #Err. +allOks : List (Result ok err) -> Result (List ok) err ## Add a single element to the end of a list. ## @@ -248,6 +252,8 @@ concat : List elem, List elem -> List elem ## >>> List.join [] join : List (List elem) -> List elem +## Like #List.map, except the transformation function wraps the return value +## in a list. At the end, all the lists get joined together into one list. joinMap : List before, (before -> List after) -> List after ## Like #List.join, but only keeps elements tagged with `Ok`. Elements @@ -263,7 +269,7 @@ joinOks : List (Result elem *) -> List elem ## Iterates over the shortest of the given lists and returns a list of `Pair` ## tags, each wrapping one of the elements in that list, along with the elements -## in the same position in # the other lists. +## in the same index in # the other lists. ## ## >>> List.zip [ "a1", "b1" "c1" ] [ "a2", "b2" ] [ "a3", "b3", "c3" ] ## @@ -271,10 +277,7 @@ joinOks : List (Result elem *) -> List elem ## ## > For a generalized version that returns whatever you like, instead of a `Pair`, ## > see `zipMap`. -zip : - List a, List b, -> List [ Pair a b ]* - List a, List b, List c, -> List [ Pair a b c ]* - List a, List b, List c, List d -> List [ Pair a b c d ]* +zip : List a, List b, -> List [ Pair a b ]* ## Like `zip` but you can specify what to do with each element. ## @@ -283,10 +286,7 @@ zip : ## >>> List.zipMap [ 1, 2, 3 ] [ 0, 5, 4 ] [ 2, 1 ] \num1 num2 num3 -> num1 + num2 - num3 ## ## Accepts up to 8 lists. -zipMap : - List a, List b, (a, b) -> List c | - List a, List b, List c, (a, b, c) -> List d | - List a, List b, List c, List d, (a, b, c, d) -> List e +zipMap : List a, List b, (a, b) -> List c ## Filter @@ -312,7 +312,7 @@ zipMap : ## If all elements in the list end up being kept, Roc will return the original ## list unaltered. ## -keepIf : List elem, (elem -> [True, False]) -> List elem +keepIf : List elem, (elem -> Bool) -> List elem ## Run the given function on each element of a list, and return all the ## elements for which the function returned `False`. @@ -323,30 +323,16 @@ keepIf : List elem, (elem -> [True, False]) -> List elem ## ## #List.dropIf has the same performance characteristics as #List.keepIf. ## See its documentation for details on those characteristics! -dropIf : List elem, (elem -> [True, False]) -> List elem - -## Takes the requested number of elements from the front of a list -## and returns them. -## -## >>> take 5 [ 1, 2, 3, 4, 5, 6, 7, 8 ] -## -## If there are fewer elements in the list than the requeted number, -## returns the entire list. -## -## >>> take 5 [ 1, 2 ] -take : List elem, Int -> List elem +dropIf : List elem, (elem -> Bool) -> List elem ## Access -## Returns the first element in the list, or `ListWasEmpty` if the list was empty. +## Returns the first element in the list, or `ListWasEmpty` if it was empty. first : List elem -> Result elem [ ListWasEmpty ]* -## Returns the last element in the list, or `ListWasEmpty` if the list was empty. +## Returns the last element in the list, or `ListWasEmpty` if it was empty. last : List elem -> Result elem [ ListWasEmpty ]* -## This takes a #Len because the maximum length of a #List is a #Len value, -## so #Len lets you specify any position up to the maximum length of -## the list. get : List elem, Len -> Result elem [ OutOfBounds ]* max : List (Num a) -> Result (Num a) [ ListWasEmpty ]* @@ -355,14 +341,17 @@ min : List (Num a) -> Result (Num a) [ ListWasEmpty ]* ## Modify -## This takes a #Len because the maximum length of a #List is a #Len value, -## so #Len lets you specify any position up to the maximum length of -## the list. -set : List elem, Len, elem -> List elem - -## Add a new element to the end of a list. +## Replaces the element at the given index with a replacement. ## -## Returns a new list with the given element as its last element. +## >>> List.put [ "a", "b", "c" ] 1 "B" +## +## If the given index is outside the bounds of the list, returns the original +## list unmodified. +put : List elem, Len, elem -> List elem + +## Adds a new element to the end of the list. +## +## >>> List.append [ "a", "b" ] "c" ## ## ## Performance Details ## @@ -372,9 +361,9 @@ set : List elem, Len, elem -> List elem ## module's documentation. append : List elem, elem -> List elem -## Add a new element to the beginning of a list. +## Adds a new element to the beginning of the list. ## -## Returns a new list with the given element as its first element. +## >>> List.prepend [ "b", "c" ] "a" ## ## ## Performance Details ## @@ -428,7 +417,7 @@ dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpt ## runs *much* faster. This is because for #List.dropLast, removing the last element ## in-place is as easy as reducing the length of the list by 1. In contrast, ## removing the first element from the list involves copying every other element -## in the list into the position before it - which is massively more costly. +## in the list into the index before it - which is massively more costly. ## ## In the case of a Shared list, ## @@ -438,41 +427,60 @@ dropLast : List elem -> Result { others : List elem, last : elem } [ ListWasEmpt ## dropLast | #List.last + clone rest of list | #List.last + clone rest of list | dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmpty ]* -## Drops the given number of elements from the end of the list. +## Returns the given number of elements from the beginning of the list. ## -## Returns a new list without the dropped elements. +## >>> List.takeFirst 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ] ## -## To remove elements from a list while also returning a list of the removed -## elements, use #List.split. +## If there are fewer elements in the list than the requested number, +## returns the entire list. ## -## To remove elements from the beginning of the list, use #List.dropFromFront. +## >>> List.takeFirst 5 [ 1, 2 ] +## +## To *remove* elements from the beginning of the list, use #List.takeLast. +## +## To remove elements from both the beginning and end of the list, +## use #List.sublist. +## +## To split the list into two lists, use #List.split. ## ## ## Performance Details ## -## When given a Unique list, this runs extremely fast. It subtracts the given -## number from the list's length (down to a minimum of 0) in-place, and that's it. +## When given a Unique list, this runs extremely fast. It sets the list's length +## to the given length value, and frees the leftover elements. This runs very +## slightly faster than #List.takeLast. ## -## In fact, `List.drop 1 list` runs faster than `List.dropLast list` when given -## a Unique list, because #List.dropLast returns the element it dropped - +## In fact, `List.takeFirst 1 list` runs faster than `List.first list` when given +## a Unique list, because #List.first returns the first element as well - ## which introduces a conditional bounds check as well as a memory load. -drop : List elem, Len -> List elem +takeFirst : List elem, Len -> List elem -## Drops the given number of elements from the front of the list. +## Returns the given number of elements from the end of the list. ## -## Returns a new list without the dropped elements. +## >>> List.takeLast 4 [ 1, 2, 3, 4, 5, 6, 7, 8 ] ## -## To remove elements from a list while also returning a list of the removed -## elements, use #List.split. +## If there are fewer elements in the list than the requested number, +## returns the entire list. +## +## >>> List.takeLast 5 [ 1, 2 ] +## +## To *remove* elements from the end of the list, use #List.takeFirst. +## +## To remove elements from both the beginning and end of the list, +## use #List.sublist. +## +## To split the list into two lists, use #List.split. ## ## ## Performance Details ## -## When given a Unique list, this runs extremely fast. It subtracts the given -## number from the list's length (down to a minimum of 0) in-place, and that's it. +## When given a Unique list, this runs extremely fast. It moves the list's +## pointer to the index at the given length value, updates its length, +## and frees the leftover elements. This runs very nearly as fast as +## #List.takeFirst on a Unique list. ## -## In fact, `List.drop 1 list` runs faster than `List.dropLast list` when given -## a Unique list, because #List.dropLast returns the element it dropped - +## In fact, `List.takeLast 1 list` runs faster than `List.first list` when given +## a Unique list, because #List.first returns the first element as well - ## which introduces a conditional bounds check as well as a memory load. -dropFromFront : List elem, Len -> List elem +takeLast : List elem, Len -> List elem ## Deconstruct @@ -485,6 +493,23 @@ dropFromFront : List elem, Len -> List elem ## `others` list will have the same elements as the original list.) split : List elem, Len -> { before: List elem, others: List elem } +## Returns a subsection of the given list, beginning at the `start` index and +## including a total of `len` elements. +## +## If `start` is outside the bounds of the given list, returns the empty list. +## +## >>> List.sublist { start: 4, len: 0 } [ 1, 2, 3 ] +## +## If more elements are requested than exist in the list, returns as many as it can. +## +## >>> List.sublist { start: 2, len: 10 } [ 1, 2, 3, 4, 5 ] +## +## > If you want a sublist which goes all the way to the end of the list, no +## > matter how long the list is, #List.takeLast can do that more efficiently. +## +## Some languages have a function called **`slice`** which works similarly to this. +sublist : List elem, { start : Len, len : Len } -> List elem + ## Build a value using each element in the list. ## ## Starting with a given `state` value, this walks through each element in the From d779e6877306aadb9dd9b6adb22aeb7e63c14fd8 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 20 Jul 2020 00:52:16 +0200 Subject: [PATCH 48/68] parse default optional expressions in pattern matches --- compiler/can/src/pattern.rs | 49 +++++++++++++- compiler/fmt/src/pattern.rs | 6 +- compiler/parse/src/ast.rs | 19 +++++- compiler/parse/src/expr.rs | 85 +++++++++++++++++++------ compiler/solve/tests/solve_expr.rs | 12 ++++ compiler/solve/tests/solve_uniq_expr.rs | 64 +++++++++++++++++++ 6 files changed, 209 insertions(+), 26 deletions(-) diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index a8bc6ba307..f1978acefd 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -1,5 +1,5 @@ use crate::env::Env; -use crate::expr::Expr; +use crate::expr::{canonicalize_expr, Expr}; use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use crate::scope::Scope; use roc_module::ident::{Ident, Lowercase, TagName}; @@ -302,6 +302,48 @@ pub fn canonicalize_pattern<'a>( }, }); } + OptionalField(label, loc_default) => { + // an optional DOES introduce the label into scope! + match scope.introduce( + label.into(), + &env.exposed_ident_ids, + &mut env.ident_ids, + region, + ) { + Ok(symbol) => { + // TODO use output? + let (can_default, _output) = canonicalize_expr( + env, + var_store, + scope, + loc_default.region, + &loc_default.value, + ); + + destructs.push(Located { + region: loc_pattern.region, + value: RecordDestruct { + var: var_store.fresh(), + label: Lowercase::from(label), + symbol, + typ: DestructType::Optional(var_store.fresh(), can_default), + }, + }); + } + Err((original_region, shadow)) => { + env.problem(Problem::RuntimeError(RuntimeError::Shadowing { + original_region, + shadow: shadow.clone(), + })); + + // No matter what the other patterns + // are, we're definitely shadowed and will + // get a runtime exception as soon as we + // encounter the first bad pattern. + opt_erroneous = Some(Pattern::Shadowed(original_region, shadow)); + } + }; + } _ => unreachable!("Any other pattern should have given a parse error"), } } @@ -315,7 +357,10 @@ pub fn canonicalize_pattern<'a>( }) } - RequiredField(_name, _loc_pattern) | OptionalField(_name, _loc_pattern) => { + RequiredField(_name, _loc_pattern) => { + unreachable!("should have been handled in RecordDestructure"); + } + OptionalField(_name, _loc_pattern) => { unreachable!("should have been handled in RecordDestructure"); } diff --git a/compiler/fmt/src/pattern.rs b/compiler/fmt/src/pattern.rs index b43f27ac27..361a4ac9df 100644 --- a/compiler/fmt/src/pattern.rs +++ b/compiler/fmt/src/pattern.rs @@ -25,9 +25,9 @@ impl<'a> Formattable<'a> for Pattern<'a> { Pattern::Nested(nested_pat) => nested_pat.is_multiline(), Pattern::RecordDestructure(fields) => fields.iter().any(|f| f.is_multiline()), - Pattern::RequiredField(_, subpattern) | Pattern::OptionalField(_, subpattern) => { - subpattern.is_multiline() - } + Pattern::RequiredField(_, subpattern) => subpattern.is_multiline(), + + Pattern::OptionalField(_, expr) => expr.is_multiline(), Pattern::Identifier(_) | Pattern::GlobalTag(_) diff --git a/compiler/parse/src/ast.rs b/compiler/parse/src/ast.rs index 1fe3242a85..0e7ba96f4b 100644 --- a/compiler/parse/src/ast.rs +++ b/compiler/parse/src/ast.rs @@ -322,7 +322,7 @@ pub enum Pattern<'a> { /// An optional field pattern, e.g. { x ? Just 0 } -> ... /// Can only occur inside of a RecordDestructure - OptionalField(&'a str, &'a Loc>), + OptionalField(&'a str, &'a Loc>), /// This is used only to avoid cloning when reordering expressions (e.g. in desugar()). /// It lets us take an (&Expr) and create a plain (Expr) from it. @@ -428,8 +428,21 @@ impl<'a> Pattern<'a> { (RequiredField(x, inner_x), RequiredField(y, inner_y)) => { x == y && inner_x.value.equivalent(&inner_y.value) } - (OptionalField(x, inner_x), OptionalField(y, inner_y)) => { - x == y && inner_x.value.equivalent(&inner_y.value) + (OptionalField(x, _inner_x), OptionalField(y, _inner_y)) => { + x == y + // TODO + // + // We can give an annotation like so + // + // { x, y } : { x : Int, y : Bool } + // { x, y } = rec + // + // But what about: + // + // { x, y ? False } : { x : Int, y ? Bool } + // { x, y ? False } = rec + // + // inner_x.value.equivalent(&inner_y.value) } (Nested(x), Nested(y)) => x.equivalent(y), diff --git a/compiler/parse/src/expr.rs b/compiler/parse/src/expr.rs index 5e7930c698..29022dfbe1 100644 --- a/compiler/parse/src/expr.rs +++ b/compiler/parse/src/expr.rs @@ -348,10 +348,9 @@ pub fn assigned_expr_field_to_pattern<'a>( } } AssignedField::OptionalValue(name, spaces, value) => { - let pattern = expr_to_pattern(arena, &value.value)?; let result = arena.alloc(Located { region: value.region, - value: pattern, + value: value.value.clone(), }); if spaces.is_empty() { Pattern::OptionalField(name.value, result) @@ -378,13 +377,13 @@ pub fn assigned_expr_field_to_pattern<'a>( /// Used for patterns like { x: Just _ } pub fn assigned_pattern_field_to_pattern<'a>( arena: &'a Bump, - assigned_field: &AssignedField<'a, Pattern<'a>>, + assigned_field: &AssignedField<'a, Expr<'a>>, backup_region: Region, ) -> Result>, Fail> { // the assigned fields always store spaces, but this slice is often empty Ok(match assigned_field { AssignedField::RequiredValue(name, spaces, value) => { - let pattern = value.value.clone(); + let pattern = expr_to_pattern(arena, &value.value)?; let region = Region::span_across(&value.region, &value.region); let result = arena.alloc(Located { region: value.region, @@ -929,22 +928,72 @@ fn underscore_pattern<'a>() -> impl Parser<'a, Pattern<'a>> { fn record_destructure<'a>(min_indent: u16) -> impl Parser<'a, Pattern<'a>> { then( - record_without_update!(loc_pattern(min_indent), min_indent), - move |arena, state, assigned_fields| { - let mut patterns = Vec::with_capacity_in(assigned_fields.len(), arena); - for assigned_field in assigned_fields { - match assigned_pattern_field_to_pattern( - arena, - &assigned_field.value, - assigned_field.region, - ) { - Ok(pattern) => patterns.push(pattern), - Err(e) => return Err((e, state)), - } - } + collection!( + char('{'), + move |arena: &'a bumpalo::Bump, + state: crate::parser::State<'a>| + -> crate::parser::ParseResult<'a, Located>> { + use crate::blankspace::{space0, space0_before}; + use crate::ident::lowercase_ident; + use crate::parser::Either::*; + use roc_region::all::Region; + // You must have a field name, e.g. "email" + let (loc_label, state) = loc!(lowercase_ident()).parse(arena, state)?; + + let (spaces, state) = space0(min_indent).parse(arena, state)?; + + // Having a value is optional; both `{ email }` and `{ email: blah }` work. + // (This is true in both literals and types.) + let (opt_loc_val, state) = crate::parser::optional(either!( + skip_first!( + char(':'), + space0_before(loc_pattern(min_indent), min_indent) + ), + skip_first!(char('?'), space0_before(loc!(expr(min_indent)), min_indent)) + )) + .parse(arena, state)?; + + let answer = match opt_loc_val { + Some(either) => match either { + First(loc_val) => Located { + region: Region::span_across(&loc_label.region, &loc_val.region), + value: Pattern::RequiredField(loc_label.value, arena.alloc(loc_val)), + }, + Second(loc_val) => Located { + region: Region::span_across(&loc_label.region, &loc_val.region), + value: Pattern::OptionalField(loc_label.value, arena.alloc(loc_val)), + }, + }, + // If no value was provided, record it as a Var. + // Canonicalize will know what to do with a Var later. + None => { + if !spaces.is_empty() { + Located { + region: loc_label.region, + value: Pattern::SpaceAfter( + arena.alloc(Pattern::Identifier(loc_label.value)), + spaces, + ), + } + } else { + Located { + region: loc_label.region, + value: Pattern::Identifier(loc_label.value), + } + } + } + }; + + Ok((answer, state)) + }, + char(','), + char('}'), + min_indent + ), + move |_arena, state, loc_patterns| { Ok(( - Pattern::RecordDestructure(patterns.into_bump_slice()), + Pattern::RecordDestructure(loc_patterns.into_bump_slice()), state, )) }, diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index 6a43144dbd..fe3d9be14f 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -2629,4 +2629,16 @@ mod solve_expr { "{ a : { x : Num a, y : Float, z : c }, b : { blah : Str, x : Num a, y : Float, z : c } }", ); } + + #[test] + fn optional_field_function() { + infer_eq_without_problem( + indoc!( + r#" + \{ x, y ? 0 } -> x + y + "# + ), + "{ x : Num a, y ? Num a }* -> Num a", + ); + } } diff --git a/compiler/solve/tests/solve_uniq_expr.rs b/compiler/solve/tests/solve_uniq_expr.rs index 14289dd6f5..3d47a59a3c 100644 --- a/compiler/solve/tests/solve_uniq_expr.rs +++ b/compiler/solve/tests/solve_uniq_expr.rs @@ -3011,4 +3011,68 @@ mod solve_uniq_expr { "Attr * (Attr Shared (Num (Attr Shared *)) -> Attr * (Num (Attr * *)))", ); } + + // OPTIONAL RECORD FIELDS + + #[test] + fn optional_field_unifies_with_missing() { + infer_eq( + indoc!( + r#" + negatePoint : { x : Int, y : Int, z ? Num c } -> { x : Int, y : Int, z : Num c } + + negatePoint { x: 1, y: 2 } + "# + ), + "Attr * { x : (Attr * Int), y : (Attr * Int), z : (Attr * (Num (Attr * c))) }", + ); + } + + #[test] + fn open_optional_field_unifies_with_missing() { + infer_eq( + indoc!( + r#" + negatePoint : { x : Int, y : Int, z ? Num c }r -> { x : Int, y : Int, z : Num c }r + + a = negatePoint { x: 1, y: 2 } + b = negatePoint { x: 1, y: 2, blah : "hi" } + + { a, b } + "# + ), + "Attr * { a : (Attr * { x : (Attr * Int), y : (Attr * Int), z : (Attr * (Num (Attr * c))) }), b : (Attr * { blah : (Attr * Str), x : (Attr * Int), y : (Attr * Int), z : (Attr * (Num (Attr * c))) }) }" + ); + } + + #[test] + fn optional_field_unifies_with_present() { + infer_eq( + indoc!( + r#" + negatePoint : { x : Num a, y : Num b, z ? c } -> { x : Num a, y : Num b, z : c } + + negatePoint { x: 1, y: 2.1, z: 0x3 } + "# + ), + "Attr * { x : (Attr * (Num (Attr * a))), y : (Attr * Float), z : (Attr * Int) }", + ); + } + + #[test] + fn open_optional_field_unifies_with_present() { + infer_eq( + indoc!( + r#" + negatePoint : { x : Num a, y : Num b, z ? c }r -> { x : Num a, y : Num b, z : c }r + + a = negatePoint { x: 1, y: 2.1 } + b = negatePoint { x: 1, y: 2.1, blah : "hi" } + + { a, b } + "# + ), + "Attr * { a : (Attr * { x : (Attr * (Num (Attr * a))), y : (Attr * Float), z : (Attr * c) }), b : (Attr * { blah : (Attr * Str), x : (Attr * (Num (Attr * a))), y : (Attr * Float), z : (Attr * c) }) }" + ); + } } From 1f55355ee0eb20515004f33b3fe6dfc0cd68bb29 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 20 Jul 2020 13:28:20 +0200 Subject: [PATCH 49/68] constrain the default --- compiler/constrain/src/pattern.rs | 4 +++- compiler/solve/tests/solve_expr.rs | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 25db18453a..9af7ebc0ae 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -246,7 +246,9 @@ pub fn constrain_pattern( state.vars.push(*expr_var); - constrain_expr(env, loc_expr.region, &loc_expr.value, expr_expected); + let expr_con = + constrain_expr(env, loc_expr.region, &loc_expr.value, expr_expected); + state.constraints.push(expr_con); RecordField::Optional(pat_type) } diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index fe3d9be14f..db4d27b26f 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -2641,4 +2641,32 @@ mod solve_expr { "{ x : Num a, y ? Num a }* -> Num a", ); } + + #[test] + fn optional_field_let() { + infer_eq_without_problem( + indoc!( + r#" + { x, y ? 0 } = { x: 32 } + + x + y + "# + ), + "Num *", + ); + } + + #[test] + fn optional_field_when() { + infer_eq_without_problem( + indoc!( + r#" + \r -> + when r is + { x, y ? 0 } -> x + y + "# + ), + "{ x : Num a, y ? Num a }* -> Num a", + ); + } } From 7bf545a757b902daff128b98a3421272fb597672 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 20 Jul 2020 13:44:45 +0200 Subject: [PATCH 50/68] constraint default unique --- compiler/constrain/src/pattern.rs | 1 - compiler/constrain/src/uniq.rs | 91 ++++++++++++++++++++++--- compiler/solve/tests/solve_uniq_expr.rs | 40 +++++++++++ 3 files changed, 123 insertions(+), 9 deletions(-) diff --git a/compiler/constrain/src/pattern.rs b/compiler/constrain/src/pattern.rs index 9af7ebc0ae..56d678ac29 100644 --- a/compiler/constrain/src/pattern.rs +++ b/compiler/constrain/src/pattern.rs @@ -230,7 +230,6 @@ pub fn constrain_pattern( RecordField::Required(pat_type) } DestructType::Optional(expr_var, loc_expr) => { - // Eq(Type, Expected, Category, Region), let expr_expected = Expected::ForReason( Reason::RecordDefaultField(label.clone()), pat_type.clone(), diff --git a/compiler/constrain/src/uniq.rs b/compiler/constrain/src/uniq.rs index 073ec5d01d..27e6e8615c 100644 --- a/compiler/constrain/src/uniq.rs +++ b/compiler/constrain/src/uniq.rs @@ -144,7 +144,10 @@ pub struct PatternState { } fn constrain_pattern( + env: &Env, var_store: &mut VarStore, + var_usage: &VarUsage, + applied_usage_constraint: &mut ImSet, state: &mut PatternState, pattern: &Located, expected: PExpected, @@ -246,14 +249,46 @@ fn constrain_pattern( PExpected::NoExpectation(pat_type.clone()), )); state.vars.push(*guard_var); - constrain_pattern(var_store, state, loc_guard, expected); + constrain_pattern( + env, + var_store, + var_usage, + applied_usage_constraint, + state, + loc_guard, + expected, + ); RecordField::Required(pat_type) } - DestructType::Optional(_expr_var, _loc_expr) => { - todo!("Add a constraint for the default value."); + DestructType::Optional(expr_var, loc_expr) => { + let expr_expected = Expected::ForReason( + Reason::RecordDefaultField(label.clone()), + pat_type.clone(), + loc_expr.region, + ); - // RecordField::Optional(pat_type) + state.constraints.push(Constraint::Eq( + Type::Variable(*expr_var), + expr_expected.clone(), + Category::DefaultValue(label.clone()), + region, + )); + + state.vars.push(*expr_var); + + let expr_con = constrain_expr( + env, + var_store, + var_usage, + applied_usage_constraint, + loc_expr.region, + &loc_expr.value, + expr_expected, + ); + state.constraints.push(expr_con); + + RecordField::Optional(pat_type) } DestructType::Required => { // No extra constraints necessary. @@ -317,7 +352,15 @@ fn constrain_pattern( argument_types.push(pattern_type.clone()); let expected = PExpected::NoExpectation(pattern_type); - constrain_pattern(var_store, state, loc_pattern, expected); + constrain_pattern( + env, + var_store, + var_usage, + applied_usage_constraint, + state, + loc_pattern, + expected, + ); } let tag_union_uniq_type = { @@ -673,7 +716,15 @@ pub fn constrain_expr( pattern_types.push(pattern_type); - constrain_pattern(var_store, &mut state, loc_pattern, pattern_expected); + constrain_pattern( + env, + var_store, + var_usage, + applied_usage_constraint, + &mut state, + loc_pattern, + pattern_expected, + ); vars.push(*pattern_var); } @@ -1690,7 +1741,10 @@ fn constrain_when_branch( for loc_pattern in &when_branch.patterns { // mutates the state, so return value is not used constrain_pattern( + env, var_store, + var_usage, + applied_usage_constraint, &mut state, &loc_pattern, pattern_expected.clone(), @@ -1743,7 +1797,10 @@ fn constrain_when_branch( } fn constrain_def_pattern( + env: &Env, var_store: &mut VarStore, + var_usage: &VarUsage, + applied_usage_constraint: &mut ImSet, loc_pattern: &Located, expr_type: Type, ) -> PatternState { @@ -1757,7 +1814,15 @@ fn constrain_def_pattern( constraints: Vec::with_capacity(1), }; - constrain_pattern(var_store, &mut state, loc_pattern, pattern_expected); + constrain_pattern( + env, + var_store, + var_usage, + applied_usage_constraint, + &mut state, + loc_pattern, + pattern_expected, + ); state } @@ -2042,7 +2107,14 @@ fn constrain_def( let expr_var = def.expr_var; let expr_type = Type::Variable(expr_var); - let mut pattern_state = constrain_def_pattern(var_store, &def.loc_pattern, expr_type.clone()); + let mut pattern_state = constrain_def_pattern( + env, + var_store, + var_usage, + applied_usage_constraint, + &def.loc_pattern, + expr_type.clone(), + ); pattern_state.vars.push(expr_var); @@ -2236,7 +2308,10 @@ pub fn rec_defs_help( pattern_state.vars.push(expr_var); constrain_pattern( + env, var_store, + var_usage, + applied_usage_constraint, &mut pattern_state, &def.loc_pattern, pattern_expected, diff --git a/compiler/solve/tests/solve_uniq_expr.rs b/compiler/solve/tests/solve_uniq_expr.rs index 3d47a59a3c..e5776d70b3 100644 --- a/compiler/solve/tests/solve_uniq_expr.rs +++ b/compiler/solve/tests/solve_uniq_expr.rs @@ -3075,4 +3075,44 @@ mod solve_uniq_expr { "Attr * { a : (Attr * { x : (Attr * (Num (Attr * a))), y : (Attr * Float), z : (Attr * c) }), b : (Attr * { blah : (Attr * Str), x : (Attr * (Num (Attr * a))), y : (Attr * Float), z : (Attr * c) }) }" ); } + + #[test] + fn optional_field_function() { + infer_eq( + indoc!( + r#" + \{ x, y ? 0 } -> x + y + "# + ), + "Attr * (Attr (* | b | c) { x : (Attr b (Num (Attr b a))), y ? (Attr c (Num (Attr c a))) }* -> Attr d (Num (Attr d a)))" + ); + } + + #[test] + fn optional_field_let() { + infer_eq( + indoc!( + r#" + { x, y ? 0 } = { x: 32 } + + x + y + "# + ), + "Attr a (Num (Attr a *))", + ); + } + + #[test] + fn optional_field_when() { + infer_eq( + indoc!( + r#" + \r -> + when r is + { x, y ? 0 } -> x + y + "# + ), + "Attr * (Attr (* | b | c) { x : (Attr b (Num (Attr b a))), y ? (Attr c (Num (Attr c a))) }* -> Attr d (Num (Attr d a)))" + ); + } } From ee42df0e7d1a48084f690504a4bd13f7a93b7221 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 20 Jul 2020 14:02:10 +0200 Subject: [PATCH 51/68] add optional defaults to usage analysis --- compiler/uniq/src/sharing.rs | 55 ++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/compiler/uniq/src/sharing.rs b/compiler/uniq/src/sharing.rs index cad34d3044..f4e00a12f9 100644 --- a/compiler/uniq/src/sharing.rs +++ b/compiler/uniq/src/sharing.rs @@ -1,4 +1,5 @@ use roc_can::expr::Expr; +use roc_can::pattern::{DestructType, Pattern}; use roc_collections::all::{ImMap, ImSet}; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; @@ -780,6 +781,38 @@ impl FieldAccess { } } +fn annotate_usage_pattern(pattern: &Pattern, usage: &mut VarUsage) { + use Pattern::*; + + match pattern { + Identifier(_) + | IntLiteral(_) + | NumLiteral(_, _) + | FloatLiteral(_) + | StrLiteral(_) + | Underscore + | Shadowed(_, _) + | UnsupportedPattern(_) + | MalformedPattern(_, _) => {} + + AppliedTag { arguments, .. } => { + for (_, loc_pattern) in arguments { + annotate_usage_pattern(&loc_pattern.value, usage); + } + } + RecordDestructure { destructs, .. } => { + use DestructType::*; + + for loc_destruct in destructs { + match &loc_destruct.value.typ { + Required | Guard(_, _) => {} + Optional(_, loc_expr) => annotate_usage(&loc_expr.value, usage), + } + } + } + } +} + pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) { use Expr::*; @@ -825,9 +858,13 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) { let mut branches_usage = VarUsage::default(); for branch in branches { - let loc_branch = &branch.value; let mut current_usage = VarUsage::default(); + // annotate defaults of optional record fields + for loc_pattern in &branch.patterns { + annotate_usage_pattern(&loc_pattern.value, usage); + } + let loc_branch = &branch.value; annotate_usage(&loc_branch.value, &mut current_usage); branches_usage.parallel(¤t_usage); @@ -844,6 +881,9 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) { LetNonRec(def, loc_expr, _, _) => { annotate_usage(&def.loc_expr.value, usage); annotate_usage(&loc_expr.value, usage); + + // annotate defaults of optional record fields + annotate_usage_pattern(&def.loc_pattern.value, usage) } LetRec(defs, loc_expr, _, _) => { // TODO test this with a practical example. @@ -853,6 +893,9 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) { for (symbol, _) in def.pattern_vars.clone() { usage.register_shared(symbol); } + // annotate defaults of optional record fields + annotate_usage_pattern(&def.loc_pattern.value, usage); + annotate_usage(&def.loc_expr.value, usage); } else { let mut rec_usage = VarUsage::default(); @@ -867,6 +910,9 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) { let mut current_usage = VarUsage::default(); annotate_usage(&def.loc_expr.value, &mut current_usage); + // annotate defaults of optional record fields + annotate_usage_pattern(&def.loc_pattern.value, usage); + let mut a = rec_usage.clone(); let b = rec_usage.clone(); @@ -912,7 +958,12 @@ pub fn annotate_usage(expr: &Expr, usage: &mut VarUsage) { annotate_usage(&arg.value, usage); } } - Closure(_, _, _, _, body) => { + Closure(_, _, _, args, body) => { + // annotate defaults of optional record fields + for (_, loc_pattern) in args { + annotate_usage_pattern(&loc_pattern.value, usage); + } + annotate_usage(&body.0.value, usage); } From 3fdcdf0da91c168788ba7c9ea5adab308f0142d0 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 20 Jul 2020 14:02:36 +0200 Subject: [PATCH 52/68] remove some dead code --- compiler/uniq/src/sharing.rs | 35 ----------------------------------- 1 file changed, 35 deletions(-) diff --git a/compiler/uniq/src/sharing.rs b/compiler/uniq/src/sharing.rs index f4e00a12f9..2300785f1a 100644 --- a/compiler/uniq/src/sharing.rs +++ b/compiler/uniq/src/sharing.rs @@ -4,10 +4,6 @@ use roc_collections::all::{ImMap, ImSet}; use roc_module::ident::Lowercase; use roc_module::symbol::Symbol; -// fake field names for container elements -// e.g. for lists, internally it's a record with a `list_elem` field -pub const LIST_ELEM: &str = "@list_elem"; - #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Mark { Seen, @@ -748,37 +744,6 @@ impl FieldAccess { pub fn get(&self, key: &Lowercase) -> Option<&Usage> { self.fields.get(key) } - - pub fn list_access() -> Self { - use Mark::*; - use Usage::*; - - let mut result = Self::default(); - result.fields.insert(LIST_ELEM.into(), Simple(Unique)); - - result - } - - pub fn list_seen() -> Self { - use Mark::*; - use Usage::*; - - let mut result = Self::default(); - result.fields.insert(LIST_ELEM.into(), Simple(Seen)); - - result - } - - pub fn list_update() -> Self { - use Mark::*; - use Usage::*; - - // TODO maybe this should be a different key so accessed items are never in overwritten and kept unique - let mut result = Self::default(); - result.fields.insert(LIST_ELEM.into(), Simple(Seen)); - - result - } } fn annotate_usage_pattern(pattern: &Pattern, usage: &mut VarUsage) { From fa893d58eb6daec430d861db933b86f3240dbcc1 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 20 Jul 2020 14:06:31 +0200 Subject: [PATCH 53/68] add usage test --- compiler/uniq/tests/test_usage_analysis.rs | 26 ++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/compiler/uniq/tests/test_usage_analysis.rs b/compiler/uniq/tests/test_usage_analysis.rs index d7add10545..c33dffc776 100644 --- a/compiler/uniq/tests/test_usage_analysis.rs +++ b/compiler/uniq/tests/test_usage_analysis.rs @@ -765,4 +765,30 @@ mod test_usage_analysis { }, ); } + + #[test] + fn record_pattern_optional_fields() { + usage_eq( + indoc!( + r#" + a = 34 + + when r is + { x, y ? a, z ? a } -> x + y + z + "# + ), + |interns| { + let mut usage = VarUsage::default(); + let home = test_home(); + + usage.register_with(interns.symbol(home, "a".into()), &Simple(Shared)); + usage.register_with(interns.symbol(home, "x".into()), &Simple(Seen)); + usage.register_with(interns.symbol(home, "y".into()), &Simple(Seen)); + usage.register_with(interns.symbol(home, "z".into()), &Simple(Seen)); + usage.register_shared(Symbol::NUM_ADD); + + usage + }, + ); + } } From d01f74330e2fd3b5c58bf7a186e03064d8e2bd06 Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 20 Jul 2020 16:05:53 +0200 Subject: [PATCH 54/68] add reporting error message tests --- compiler/reporting/tests/test_reporting.rs | 146 ++++++++++++++++++++- 1 file changed, 144 insertions(+), 2 deletions(-) diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index a368cc7385..29b29d2b73 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -3112,13 +3112,13 @@ mod test_reporting { ┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ This `ACons` global tag application has the type: - + [ ACons (Num Integer) [ BCons (Num Integer) [ ACons Str [ BCons Int [ ACons Int (BList Int Int), ANil ] as a, BNil ], ANil ], BNil ], ANil ] But the type annotation on `x` says it should be: - + [ ACons Int (BList Int Int), ANil ] as a "# ), @@ -3456,4 +3456,146 @@ mod test_reporting { ), ) } + + #[test] + fn optional_record_default_type_error() { + report_problem_as( + indoc!( + r#" + \{ x, y ? True } -> x + y + "# + ), + indoc!( + r#" + -- TYPE MISMATCH --------------------------------------------------------------- + + The 2nd argument to `add` is not what I expect: + + 1 ┆ \{ x, y ? True } -> x + y + ┆ ^ + + This `y` value is a: + + [ True ]a + + But `add` needs the 2nd argument to be: + + Num a + "# + ), + ) + } + + #[test] + fn optional_record_default_with_signature() { + report_problem_as( + indoc!( + r#" + f : { x : Int, y ? Int } -> Int + f = \{ x, y ? "foo" } -> x + y + + f + "# + ), + indoc!( + r#" + -- TYPE MISMATCH --------------------------------------------------------------- + + The 2nd argument to `add` is not what I expect: + + 2 ┆ f = \{ x, y ? "foo" } -> x + y + ┆ ^ + + This `y` value is a: + + Str + + But `add` needs the 2nd argument to be: + + Num a + "# + ), + ) + } + + #[test] + fn guard_mismatch_with_annotation() { + // TODO improve this message + // The problem is the guard, and the message should point to that + report_problem_as( + indoc!( + r#" + f : { x : Int, y : Int } -> Int + f = \r -> + when r is + { x, y : "foo" } -> x + 0 + _ -> 0 + + f + "# + ), + indoc!( + r#" + -- TYPE MISMATCH --------------------------------------------------------------- + + Something is off with the body of the `f` definition: + + 1 ┆ f : { x : Int, y : Int } -> Int + 2 ┆> f = \r -> + 3 ┆> when r is + 4 ┆> { x, y : "foo" } -> x + 0 + 5 ┆> _ -> 0 + + The body is an anonymous function of type: + + { x : Num Integer, y : Str } -> Num Integer + + But the type annotation on `f` says it should be: + + { x : Int, y : Int } -> Int + "# + ), + ) + } + + #[test] + fn optional_field_mismatch_with_annotation() { + // TODO improve this message + // The problem is the pattern match default + // the message should point to that. + report_problem_as( + indoc!( + r#" + f : { x : Int, y ? Int } -> Int + f = \r -> + when r is + { x, y ? "foo" } -> (\g, _ -> g) x y + _ -> 0 + + f + "# + ), + indoc!( + r#" + -- TYPE MISMATCH --------------------------------------------------------------- + + Something is off with the body of the `f` definition: + + 1 ┆ f : { x : Int, y ? Int } -> Int + 2 ┆> f = \r -> + 3 ┆> when r is + 4 ┆> { x, y ? "foo" } -> (\g, _ -> g) x y + 5 ┆> _ -> 0 + + The body is an anonymous function of type: + + { x : Num Integer, y : Str } -> Num Integer + + But the type annotation on `f` says it should be: + + { x : Int, y : Int } -> Int + "# + ), + ) + } } From 02675d613bab71d1cf3666a8c21c2f036547977e Mon Sep 17 00:00:00 2001 From: Folkert Date: Mon, 20 Jul 2020 21:38:21 +0200 Subject: [PATCH 55/68] improved error messages for function definitions --- compiler/constrain/src/expr.rs | 130 +++++++++++++++++++-- compiler/reporting/src/error/type.rs | 86 +++++++++++--- compiler/reporting/tests/test_reporting.rs | 106 ++++++++--------- compiler/types/src/types.rs | 24 +++- 4 files changed, 267 insertions(+), 79 deletions(-) diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index a646e03275..70259ea39b 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -307,7 +307,7 @@ pub fn constrain_expr( } Var(symbol) => Lookup(*symbol, expected, region), Closure(fn_var, _symbol, _recursive, args, boxed) => { - let (loc_body_expr, ret_var) = &**boxed; + let (loc_body_expr, ret_var) = boxed.as_ref(); let mut state = PatternState { headers: SendMap::default(), vars: Vec::with_capacity(args.len()), @@ -1001,13 +1001,18 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { &mut pattern_state.headers, ); + let env = &Env { + home: env.home, + rigids: ftv, + }; + let annotation_expected = FromAnnotation( def.loc_pattern.clone(), arity, AnnotationSource::TypedBody { region: annotation.region, }, - signature, + signature.clone(), ); pattern_state.constraints.push(Eq( @@ -1017,15 +1022,118 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { annotation.region, )); - constrain_expr( - &Env { - home: env.home, - rigids: ftv, - }, - def.loc_expr.region, - &def.loc_expr.value, - annotation_expected, - ) + // when a def is annotated, and it's body is a closure, treat this + // as a named function (in elm terms) for error messages. + // + // This means we get errors like "the first argument of `f` is weird" + // instead of the more generic "something is wrong with the body of `f`" + match (&def.loc_expr.value, &signature) { + ( + Closure(fn_var, _symbol, _recursive, args, boxed), + Type::Function(arg_types, _), + ) if true => { + let expected = annotation_expected; + let region = def.loc_expr.region; + + let (loc_body_expr, ret_var) = boxed.as_ref(); + let mut state = PatternState { + headers: SendMap::default(), + vars: Vec::with_capacity(args.len()), + constraints: Vec::with_capacity(1), + }; + let mut vars = Vec::with_capacity(state.vars.capacity() + 1); + let mut pattern_types = Vec::with_capacity(state.vars.capacity()); + let ret_var = *ret_var; + let ret_type = Type::Variable(ret_var); + + vars.push(ret_var); + + let it = args.iter().zip(arg_types.iter()).enumerate(); + for (index, ((pattern_var, loc_pattern), loc_ann)) in it { + { + // ensure type matches the one in the annotation + let opt_label = + if let Pattern::Identifier(label) = def.loc_pattern.value { + Some(label) + } else { + None + }; + let pattern_type: &Type = loc_ann; + + let pattern_expected = PExpected::ForReason( + PReason::TypedArg { + index: Index::zero_based(index), + opt_name: opt_label, + }, + pattern_type.clone(), + loc_pattern.region, + ); + + constrain_pattern( + env, + &loc_pattern.value, + loc_pattern.region, + pattern_expected, + &mut state, + ); + } + + { + // record the correct type in pattern_var + let pattern_type = Type::Variable(*pattern_var); + pattern_types.push(pattern_type.clone()); + + state.vars.push(*pattern_var); + state.constraints.push(Constraint::Eq( + pattern_type.clone(), + Expected::NoExpectation(loc_ann.clone()), + Category::Storage, + loc_pattern.region, + )); + + vars.push(*pattern_var); + } + } + + let fn_type = Type::Function(pattern_types, Box::new(ret_type.clone())); + let body_type = NoExpectation(ret_type); + let ret_constraint = + constrain_expr(env, loc_body_expr.region, &loc_body_expr.value, body_type); + + vars.push(*fn_var); + let defs_constraint = And(state.constraints); + + exists( + vars, + And(vec![ + Let(Box::new(LetConstraint { + rigid_vars: Vec::new(), + flex_vars: state.vars, + def_types: state.headers, + def_aliases: SendMap::default(), + defs_constraint, + ret_constraint, + })), + // "the closure's type is equal to expected type" + Eq(fn_type.clone(), expected, Category::Lambda, region), + // "fn_var is equal to the closure's type" - fn_var is used in code gen + Eq( + Type::Variable(*fn_var), + NoExpectation(fn_type), + Category::Storage, + region, + ), + ]), + ) + } + + _ => constrain_expr( + &env, + def.loc_expr.region, + &def.loc_expr.value, + annotation_expected, + ), + } } None => constrain_expr( env, diff --git a/compiler/reporting/src/error/type.rs b/compiler/reporting/src/error/type.rs index c4ed0bc303..71f38302c5 100644 --- a/compiler/reporting/src/error/type.rs +++ b/compiler/reporting/src/error/type.rs @@ -953,6 +953,45 @@ fn to_pattern_report<'b>( } PExpected::ForReason(reason, expected_type, region) => match reason { + PReason::TypedArg { opt_name, index } => { + let name = match opt_name { + Some(n) => alloc.symbol_unqualified(n), + None => alloc.text(" this definition "), + }; + let doc = alloc.stack(vec![ + alloc + .text("The ") + .append(alloc.text(index.ordinal())) + .append(alloc.text(" argument to ")) + .append(name.clone()) + .append(alloc.text(" is weird:")), + alloc.region(region), + pattern_type_comparision( + alloc, + found, + expected_type, + add_pattern_category( + alloc, + alloc.text("The argument is a pattern that matches"), + &category, + ), + alloc.concat(vec![ + alloc.text("But the annotation on "), + name, + alloc.text(" says the "), + alloc.text(index.ordinal()), + alloc.text(" argument should be:"), + ]), + vec![], + ), + ]); + + Report { + filename, + title: "TYPE MISMATCH".to_string(), + doc, + } + } PReason::WhenMatch { index } => { if index == Index::FIRST { let doc = alloc.stack(vec![ @@ -1295,10 +1334,17 @@ pub fn to_doc<'b>( alloc, fields .into_iter() - .map(|(k, v)| { + .map(|(k, value)| { ( alloc.string(k.as_str().to_string()), - to_doc(alloc, Parens::Unnecessary, v.into_inner()), + match value { + RecordField::Optional(v) => { + RecordField::Optional(to_doc(alloc, Parens::Unnecessary, v)) + } + RecordField::Required(v) => { + RecordField::Required(to_doc(alloc, Parens::Unnecessary, v)) + } + }, ) }) .collect(), @@ -1594,12 +1640,18 @@ fn diff_record<'b>( left: ( field.clone(), alloc.string(field.as_str().to_string()), - diff.left, + match t1 { + RecordField::Optional(_) => RecordField::Optional(diff.left), + RecordField::Required(_) => RecordField::Required(diff.left), + }, ), right: ( field.clone(), alloc.string(field.as_str().to_string()), - diff.right, + match t2 { + RecordField::Optional(_) => RecordField::Optional(diff.right), + RecordField::Required(_) => RecordField::Required(diff.right), + }, ), status: diff.status, } @@ -1608,7 +1660,7 @@ fn diff_record<'b>( ( field.clone(), alloc.string(field.as_str().to_string()), - to_doc(alloc, Parens::Unnecessary, tipe.clone().into_inner()), + tipe.map(|t| to_doc(alloc, Parens::Unnecessary, t.clone())), ) }; let shared_keys = fields1 @@ -1661,11 +1713,12 @@ fn diff_record<'b>( let ext_diff = ext_to_diff(alloc, ext1, ext2); - let mut fields_diff: Diff, RocDocBuilder<'b>)>> = Diff { - left: vec![], - right: vec![], - status: Status::Similar, - }; + let mut fields_diff: Diff, RecordField>)>> = + Diff { + left: vec![], + right: vec![], + status: Status::Similar, + }; for diff in both { fields_diff.left.push(diff.left); @@ -1938,7 +1991,7 @@ mod report_text { pub fn record<'b>( alloc: &'b RocDocAllocator<'b>, - entries: Vec<(RocDocBuilder<'b>, RocDocBuilder<'b>)>, + entries: Vec<(RocDocBuilder<'b>, RecordField>)>, opt_ext: Option>, ) -> RocDocBuilder<'b> { let ext_doc = if let Some(t) = opt_ext { @@ -1951,8 +2004,15 @@ mod report_text { alloc.text("{}").append(ext_doc) } else { let entry_to_doc = - |(field_name, field_type): (RocDocBuilder<'b>, RocDocBuilder<'b>)| { - field_name.append(alloc.text(" : ")).append(field_type) + |(field_name, field_type): (RocDocBuilder<'b>, RecordField>)| { + match field_type { + RecordField::Required(field) => { + field_name.append(alloc.text(" : ")).append(field) + } + RecordField::Optional(field) => { + field_name.append(alloc.text(" ? ")).append(field) + } + } }; let starts = diff --git a/compiler/reporting/tests/test_reporting.rs b/compiler/reporting/tests/test_reporting.rs index 29b29d2b73..27c12ef16c 100644 --- a/compiler/reporting/tests/test_reporting.rs +++ b/compiler/reporting/tests/test_reporting.rs @@ -1630,6 +1630,21 @@ mod test_reporting { #[test] fn bad_double_rigid() { + // this previously reported the message below, not sure which is better + // + // Something is off with the body of the `f` definition: + // + // 1 ┆ f : a, b -> a + // 2 ┆ f = \x, y -> if True then x else y + // ┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + // + // The body is an anonymous function of type: + // + // a, a -> a + // + // But the type annotation on `f` says it should be: + // + // a, b -> a report_problem_as( indoc!( r#" @@ -1643,21 +1658,22 @@ mod test_reporting { r#" -- TYPE MISMATCH --------------------------------------------------------------- - Something is off with the body of the `f` definition: + This `if` has an `else` branch with a different type from its `then` branch: - 1 ┆ f : a, b -> a 2 ┆ f = \x, y -> if True then x else y - ┆ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + ┆ ^ - The body is an anonymous function of type: + This `y` value is a: - a, a -> a + b - But the type annotation on `f` says it should be: + but the `then` branch has the type: - a, b -> a + a - Hint: Your type annotation uses `a` and `b` as separate type variables. + I need all branches in an `if` to have the same type! + + Hint: Your type annotation uses `b` and `a` as separate type variables. Your code seems to be saying they are the same though. Maybe they should be the same your type annotation? Maybe your code uses them in a weird way? @@ -1958,27 +1974,17 @@ mod test_reporting { r#" -- TYPE MISMATCH --------------------------------------------------------------- - Something is off with the body of the `f` definition: + The `r` record does not have a `.foo` field: - 1 ┆ f : { fo: Int }ext -> Int - 2 ┆> f = \r -> - 3 ┆> r2 = { r & foo: r.fo } - 4 ┆> - 5 ┆> r2.fo + 3 ┆ r2 = { r & foo: r.fo } + ┆ ^^^^^^^^^ - The body is an anonymous function of type: + This is usually a typo. Here are the `r` fields that are most similar: - { fo : Int, foo : Int }a -> Int + { fo : Int + }ext - But the type annotation on `f` says it should be: - - { fo : Int }ext -> Int - - Hint: Seems like a record field typo. Maybe `foo` should be `fo`? - - Hint: Can more type annotations be added? Type annotations always help - me give more specific messages, and I think they could help a lot in - this case + So maybe `.foo` should be `.fo`? "# ), ) @@ -3492,7 +3498,7 @@ mod test_reporting { indoc!( r#" f : { x : Int, y ? Int } -> Int - f = \{ x, y ? "foo" } -> x + y + f = \{ x, y ? "foo" } -> (\g, _ -> g) x y f "# @@ -3501,18 +3507,18 @@ mod test_reporting { r#" -- TYPE MISMATCH --------------------------------------------------------------- - The 2nd argument to `add` is not what I expect: + The 1st argument to `f` is weird: - 2 ┆ f = \{ x, y ? "foo" } -> x + y - ┆ ^ + 2 ┆ f = \{ x, y ? "foo" } -> (\g, _ -> g) x y + ┆ ^^^^^^^^^^^^^^^^ - This `y` value is a: + The argument is a pattern that matches record values of type: - Str + { x : Int, y ? Str } - But `add` needs the 2nd argument to be: + But the annotation on `f` says the 1st argument should be: - Num a + { x : Int, y ? Int } "# ), ) @@ -3538,21 +3544,18 @@ mod test_reporting { r#" -- TYPE MISMATCH --------------------------------------------------------------- - Something is off with the body of the `f` definition: + The 1st pattern in this `when` is causing a mismatch: - 1 ┆ f : { x : Int, y : Int } -> Int - 2 ┆> f = \r -> - 3 ┆> when r is - 4 ┆> { x, y : "foo" } -> x + 0 - 5 ┆> _ -> 0 + 4 ┆ { x, y : "foo" } -> x + 0 + ┆ ^^^^^^^^^^^^^^^^ - The body is an anonymous function of type: + The first pattern is trying to match record values of type: - { x : Num Integer, y : Str } -> Num Integer + { x : Int, y : Str } - But the type annotation on `f` says it should be: + But the expression between `when` and `is` has the type: - { x : Int, y : Int } -> Int + { x : Int, y : Int } "# ), ) @@ -3579,21 +3582,18 @@ mod test_reporting { r#" -- TYPE MISMATCH --------------------------------------------------------------- - Something is off with the body of the `f` definition: + The 1st pattern in this `when` is causing a mismatch: - 1 ┆ f : { x : Int, y ? Int } -> Int - 2 ┆> f = \r -> - 3 ┆> when r is - 4 ┆> { x, y ? "foo" } -> (\g, _ -> g) x y - 5 ┆> _ -> 0 + 4 ┆ { x, y ? "foo" } -> (\g, _ -> g) x y + ┆ ^^^^^^^^^^^^^^^^ - The body is an anonymous function of type: + The first pattern is trying to match record values of type: - { x : Num Integer, y : Str } -> Num Integer + { x : Int, y ? Str } - But the type annotation on `f` says it should be: + But the expression between `when` and `is` has the type: - { x : Int, y : Int } -> Int + { x : Int, y ? Int } "# ), ) diff --git a/compiler/types/src/types.rs b/compiler/types/src/types.rs index ae41d94814..0fab169b0b 100644 --- a/compiler/types/src/types.rs +++ b/compiler/types/src/types.rs @@ -41,6 +41,17 @@ impl RecordField { Required(t) => t, } } + + pub fn map(&self, mut f: F) -> RecordField + where + F: FnMut(&T) -> U, + { + use RecordField::*; + match self { + Optional(t) => Optional(f(t)), + Required(t) => Required(f(t)), + } + } } impl RecordField { @@ -820,8 +831,17 @@ pub struct RecordStructure { #[derive(Debug, Clone, PartialEq, Eq)] pub enum PReason { - WhenMatch { index: Index }, - TagArg { tag_name: TagName, index: Index }, + TypedArg { + opt_name: Option, + index: Index, + }, + WhenMatch { + index: Index, + }, + TagArg { + tag_name: TagName, + index: Index, + }, PatternGuard, } From edd1a42bec9426090d7e81d39c890c2ad2e43d26 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 20 Jul 2020 21:44:39 -0400 Subject: [PATCH 56/68] Reproduce unused optional fields bug --- compiler/can/tests/test_can.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/compiler/can/tests/test_can.rs b/compiler/can/tests/test_can.rs index 8deac2c668..2b6e4ce908 100644 --- a/compiler/can/tests/test_can.rs +++ b/compiler/can/tests/test_can.rs @@ -662,6 +662,24 @@ mod test_can { assert_eq!(problems, Vec::new()); } + #[test] + fn optional_field_not_unused() { + let src = indoc!( + r#" + fallbackZ = 3 + + fn = \{ x, y, z ? fallbackZ } -> + { x, y, z } + + fn { x: 0, y: 1 } + "# + ); + let arena = Bump::new(); + let CanExprOut { problems, .. } = can_expr_with(&arena, test_home(), src); + + assert_eq!(problems, Vec::new()); + } + //#[test] //fn closing_over_locals() { // // "local" should be used, because the closure used it. From a58ef99518a62735e076007f3a4f5b68ef552a83 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 20 Jul 2020 21:56:16 -0400 Subject: [PATCH 57/68] Count default exprs as having used things --- compiler/can/src/def.rs | 52 +++++++++++++++++++++++-------------- compiler/can/src/expr.rs | 18 ++++++++++--- compiler/can/src/pattern.rs | 48 ++++++++++++++++++++-------------- 3 files changed, 75 insertions(+), 43 deletions(-) diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 79f71f6608..4e9b349f7b 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -148,7 +148,7 @@ pub fn canonicalize_defs<'a>( // Any time we have an Annotation followed immediately by a Body, // check to see if their patterns are equivalent. If they are, // turn it into a TypedBody. Otherwise, give an error. - let pending_def = match &loc_def.value { + let (new_output, pending_def) = match &loc_def.value { Annotation(pattern, annotation) | Nested(Annotation(pattern, annotation)) => { match iter.peek() { Some(Located { @@ -189,6 +189,8 @@ pub fn canonicalize_defs<'a>( _ => to_pending_def(env, var_store, &loc_def.value, &mut scope, pattern_type), }; + output.union(new_output); + // Record the ast::Expr for later. We'll do another pass through these // once we have the entire scope assembled. If we were to canonicalize // the exprs right now, they wouldn't have symbols in scope from defs @@ -212,8 +214,8 @@ pub fn canonicalize_defs<'a>( let mut can_vars: Vec> = Vec::with_capacity(vars.len()); - let mut is_phantom = false; + for loc_lowercase in vars { if let Some(var) = can_ann .introduced_variables @@ -1288,13 +1290,13 @@ fn to_pending_def<'a>( def: &'a ast::Def<'a>, scope: &mut Scope, pattern_type: PatternType, -) -> PendingDef<'a> { +) -> (Output, PendingDef<'a>) { use roc_parse::ast::Def::*; match def { Annotation(loc_pattern, loc_ann) => { // This takes care of checking for shadowing and adding idents to scope. - let loc_can_pattern = canonicalize_pattern( + let (output, loc_can_pattern) = canonicalize_pattern( env, var_store, scope, @@ -1303,11 +1305,14 @@ fn to_pending_def<'a>( loc_pattern.region, ); - PendingDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann) + ( + output, + PendingDef::AnnotationOnly(loc_pattern, loc_can_pattern, loc_ann), + ) } Body(loc_pattern, loc_expr) => { // This takes care of checking for shadowing and adding idents to scope. - let loc_can_pattern = canonicalize_pattern( + let (output, loc_can_pattern) = canonicalize_pattern( env, var_store, scope, @@ -1316,7 +1321,10 @@ fn to_pending_def<'a>( loc_pattern.region, ); - PendingDef::Body(loc_pattern, loc_can_pattern, loc_expr) + ( + output, + PendingDef::Body(loc_pattern, loc_can_pattern, loc_expr), + ) } TypedBody(loc_pattern, loc_ann, loc_expr) => pending_typed_body( env, @@ -1358,19 +1366,22 @@ fn to_pending_def<'a>( region: loc_var.region, }); - return PendingDef::InvalidAlias; + return (Output::default(), PendingDef::InvalidAlias); } } } - PendingDef::Alias { - name: Located { - region: name.region, - value: symbol, + ( + Output::default(), + PendingDef::Alias { + name: Located { + region: name.region, + value: symbol, + }, + vars: can_rigids, + ann, }, - vars: can_rigids, - ann, - } + ) } Err((original_region, loc_shadowed_symbol)) => { @@ -1379,7 +1390,7 @@ fn to_pending_def<'a>( shadow: loc_shadowed_symbol, }); - PendingDef::InvalidAlias + (Output::default(), PendingDef::InvalidAlias) } } } @@ -1398,9 +1409,9 @@ fn pending_typed_body<'a>( var_store: &mut VarStore, scope: &mut Scope, pattern_type: PatternType, -) -> PendingDef<'a> { +) -> (Output, PendingDef<'a>) { // This takes care of checking for shadowing and adding idents to scope. - let loc_can_pattern = canonicalize_pattern( + let (output, loc_can_pattern) = canonicalize_pattern( env, var_store, scope, @@ -1409,7 +1420,10 @@ fn pending_typed_body<'a>( loc_pattern.region, ); - PendingDef::TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) + ( + output, + PendingDef::TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr), + ) } /// Make aliases recursive diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 21e031270d..ea14a43040 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -427,9 +427,10 @@ pub fn canonicalize_expr<'a>( let original_scope = scope; let mut scope = original_scope.clone(); let mut can_args = Vec::with_capacity(loc_arg_patterns.len()); + let mut output = Output::default(); for loc_pattern in loc_arg_patterns.into_iter() { - let can_arg = canonicalize_pattern( + let (new_output, can_arg) = canonicalize_pattern( env, var_store, &mut scope, @@ -438,16 +439,21 @@ pub fn canonicalize_expr<'a>( loc_pattern.region, ); + output.union(new_output); + can_args.push((var_store.fresh(), can_arg)); } - let (loc_body_expr, mut output) = canonicalize_expr( + let (loc_body_expr, new_output) = canonicalize_expr( env, var_store, &mut scope, loc_body_expr.region, &loc_body_expr.value, ); + + output.union(new_output); + // Now that we've collected all the references, check to see if any of the args we defined // went unreferenced. If any did, report them as unused arguments. for (sub_symbol, region) in scope.symbols() { @@ -713,14 +719,18 @@ fn canonicalize_when_branch<'a>( // TODO report symbols not bound in all patterns for loc_pattern in &branch.patterns { - patterns.push(canonicalize_pattern( + let (new_output, can_pattern) = canonicalize_pattern( env, var_store, &mut scope, WhenBranch, &loc_pattern.value, loc_pattern.region, - )); + ); + + output.union(new_output); + + patterns.push(can_pattern); } let (value, mut branch_output) = canonicalize_expr( diff --git a/compiler/can/src/pattern.rs b/compiler/can/src/pattern.rs index f1978acefd..70dbae9c67 100644 --- a/compiler/can/src/pattern.rs +++ b/compiler/can/src/pattern.rs @@ -1,5 +1,5 @@ use crate::env::Env; -use crate::expr::{canonicalize_expr, Expr}; +use crate::expr::{canonicalize_expr, Expr, Output}; use crate::num::{finish_parsing_base, finish_parsing_float, finish_parsing_int}; use crate::scope::Scope; use roc_module::ident::{Ident, Lowercase, TagName}; @@ -100,10 +100,11 @@ pub fn canonicalize_pattern<'a>( pattern_type: PatternType, pattern: &ast::Pattern<'a>, region: Region, -) -> Located { +) -> (Output, Located) { use roc_parse::ast::Pattern::*; use PatternType::*; + let mut output = Output::default(); let can_pattern = match pattern { Identifier(name) => match scope.introduce( (*name).into(), @@ -154,17 +155,18 @@ pub fn canonicalize_pattern<'a>( let mut can_patterns = Vec::with_capacity(patterns.len()); for loc_pattern in *patterns { - can_patterns.push(( - var_store.fresh(), - canonicalize_pattern( - env, - var_store, - scope, - pattern_type, - &loc_pattern.value, - loc_pattern.region, - ), - )); + let (new_output, can_pattern) = canonicalize_pattern( + env, + var_store, + scope, + pattern_type, + &loc_pattern.value, + loc_pattern.region, + ); + + output.union(new_output); + + can_patterns.push((var_store.fresh(), can_pattern)); } Pattern::AppliedTag { @@ -283,7 +285,7 @@ pub fn canonicalize_pattern<'a>( RequiredField(label, loc_guard) => { // a guard does not introduce the label into scope! let symbol = scope.ignore(label.into(), &mut env.ident_ids); - let can_guard = canonicalize_pattern( + let (new_output, can_guard) = canonicalize_pattern( env, var_store, scope, @@ -292,6 +294,8 @@ pub fn canonicalize_pattern<'a>( loc_guard.region, ); + output.union(new_output); + destructs.push(Located { region: loc_pattern.region, value: RecordDestruct { @@ -311,8 +315,7 @@ pub fn canonicalize_pattern<'a>( region, ) { Ok(symbol) => { - // TODO use output? - let (can_default, _output) = canonicalize_expr( + let (can_default, expr_output) = canonicalize_expr( env, var_store, scope, @@ -320,6 +323,8 @@ pub fn canonicalize_pattern<'a>( &loc_default.value, ); + output.union(expr_output); + destructs.push(Located { region: loc_pattern.region, value: RecordDestruct { @@ -375,10 +380,13 @@ pub fn canonicalize_pattern<'a>( } }; - Located { - region, - value: can_pattern, - } + ( + output, + Located { + region, + value: can_pattern, + }, + ) } /// When we detect an unsupported pattern type (e.g. 5 = 1 + 2 is unsupported because you can't From fc98713ede4349aa470599e3a1c8494fcd8d552a Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 21 Jul 2020 01:17:24 -0400 Subject: [PATCH 58/68] Drop unused argument --- editor/src/lib.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 2868a32112..83597100cf 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -151,7 +151,6 @@ fn run_event_loop() -> Result<(), Box> { } => { if let Some(virtual_keycode) = input.virtual_keycode { handle_keydown( - &mut text_state, input.state, virtual_keycode, keyboard_modifiers, @@ -226,7 +225,6 @@ fn run_event_loop() -> Result<(), Box> { } fn handle_keydown( - text_state: &mut String, elem_state: ElementState, virtual_keycode: VirtualKeyCode, _modifiers: ModifiersState, From c450381a8f15dcccdafe304c9dc4149f83bcab89 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 21 Jul 2020 01:34:30 -0400 Subject: [PATCH 59/68] cargo fmt --- editor/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/editor/src/lib.rs b/editor/src/lib.rs index 83597100cf..a26b8fbafa 100644 --- a/editor/src/lib.rs +++ b/editor/src/lib.rs @@ -150,11 +150,7 @@ fn run_event_loop() -> Result<(), Box> { .. } => { if let Some(virtual_keycode) = input.virtual_keycode { - handle_keydown( - input.state, - virtual_keycode, - keyboard_modifiers, - ); + handle_keydown(input.state, virtual_keycode, keyboard_modifiers); } } winit::event::Event::WindowEvent { From 2465c204e3caf589cb0e6d6b08ebe4860d723c0c Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 21 Jul 2020 01:35:10 -0400 Subject: [PATCH 60/68] Restore CLI --- Cargo.lock | 145 ++++++++++++- Cargo.toml | 3 +- cli/src/main.rs | 398 +--------------------------------- cli/src/repl.rs | 44 ++-- compiler/build/src/program.rs | 2 +- 5 files changed, 181 insertions(+), 411 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e11fcd579f..2dd5ed532a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -207,6 +207,35 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "clap" +version = "3.0.0-beta.1" +source = "git+https://github.com/rtfeldman/clap#e1d83a78804a271b053d4d21f69b67f7eb01ad01" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "strsim", + "termcolor", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap_derive" +version = "3.0.0-beta.1" +source = "git+https://github.com/rtfeldman/clap#e1d83a78804a271b053d4d21f69b67f7eb01ad01" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", +] + [[package]] name = "cloudabi" version = "0.0.3" @@ -329,7 +358,7 @@ checksum = "1fc755679c12bda8e5523a71e4d654b6bf2e14bd838dfc48cde6559a05caf7d1" dependencies = [ "atty", "cast", - "clap", + "clap 2.33.0", "criterion-plot", "csv", "itertools", @@ -856,6 +885,24 @@ dependencies = [ "xi-unicode", ] +[[package]] +name = "hashbrown" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34f595585f103464d8d2f6e9864682d74c1601fed5e07d62b1c9058dba8246fb" +dependencies = [ + "autocfg 1.0.0", +] + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "hermit-abi" version = "0.1.10" @@ -911,6 +958,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "indexmap" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b88cd59ee5f71fea89a62248fc8f387d44400cefe05ef548466d61ced9029a7" +dependencies = [ + "autocfg 1.0.0", + "hashbrown", +] + [[package]] name = "indoc" version = "0.3.5" @@ -1434,6 +1491,32 @@ dependencies = [ "difference", ] +[[package]] +name = "proc-macro-error" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18f33027081eba0a6d8aba6d1b1c3a3be58cbb12106341c2d5759fcd9b5277e7" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a5b4b77fdb63c1eca72173d68d24501c54ab1269409f6b672c85deb18af69de" +dependencies = [ + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", + "syn-mid", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.15" @@ -1757,6 +1840,43 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "roc-cli" +version = "0.1.0" +dependencies = [ + "bumpalo", + "clap 3.0.0-beta.1", + "im", + "im-rc", + "indoc", + "inkwell", + "inlinable_string", + "maplit", + "pretty_assertions", + "quickcheck", + "quickcheck_macros", + "roc_build", + "roc_builtins", + "roc_can", + "roc_collections", + "roc_constrain", + "roc_editor", + "roc_gen", + "roc_load", + "roc_module", + "roc_mono", + "roc_parse", + "roc_problem", + "roc_region", + "roc_reporting", + "roc_solve", + "roc_types", + "roc_unify", + "roc_uniq", + "target-lexicon", + "tokio", +] + [[package]] name = "roc_build" version = "0.1.0" @@ -2361,6 +2481,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "strsim" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c" + [[package]] name = "syn" version = "0.15.44" @@ -2383,6 +2509,17 @@ dependencies = [ "unicode-xid 0.2.0", ] +[[package]] +name = "syn-mid" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" +dependencies = [ + "proc-macro2 1.0.10", + "quote 1.0.3", + "syn 1.0.17", +] + [[package]] name = "synstructure" version = "0.12.4" @@ -2499,6 +2636,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + [[package]] name = "unicode-width" version = "0.1.7" diff --git a/Cargo.toml b/Cargo.toml index 8de62c5b0a..1bf23787c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,8 @@ members = [ "vendor/ena", "vendor/pathfinding", "vendor/pretty", - "editor" + "editor", + "cli" ] # Optimizations based on https://deterministic.space/high-performance-rust.html diff --git a/cli/src/main.rs b/cli/src/main.rs index 5932ce29f6..4d3bead09e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -2,33 +2,17 @@ extern crate clap; use bumpalo::Bump; -use inkwell::context::Context; -use inkwell::module::Linkage; -use inkwell::passes::PassManager; -use inkwell::types::BasicType; -use inkwell::OptimizationLevel; -use roc_collections::all::ImMap; use roc_collections::all::MutMap; -use roc_gen::layout_id::LayoutIds; -use roc_gen::llvm::build::{ - build_proc, build_proc_header, get_call_conventions, module_from_builtins, OptLevel, -}; -use roc_gen::llvm::convert::basic_type_from_layout; -use roc_build::program::build; -use roc_load::file::{LoadedModule, LoadingProblem}; -use roc_module::symbol::Symbol; -use roc_mono::expr::{Env, Expr, PartialProc, Procs}; -use roc_mono::layout::Layout; +use roc_gen::llvm::build::OptLevel; +use roc_build::program::gen; +use roc_load::file::LoadingProblem; use std::time::SystemTime; use clap::{App, Arg, ArgMatches}; -use inkwell::targets::{ - CodeModel, FileType, InitializationConfig, RelocMode, Target, TargetTriple, -}; use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; use std::process; -use target_lexicon::{Architecture, Environment, OperatingSystem, Triple, Vendor}; +use target_lexicon::Triple; use tokio::process::Command; use tokio::runtime::Builder; @@ -247,377 +231,3 @@ async fn build_file( Ok(binary_path) } - - -// TODO this should probably use more helper functions -#[allow(clippy::cognitive_complexity)] -fn gen( - arena: &Bump, - loaded: LoadedModule, - filename: PathBuf, - target: Triple, - dest_filename: &Path, - opt_level: OptLevel, -) { - use roc_reporting::report::{can_problem, type_problem, RocDocAllocator, DEFAULT_PALETTE}; - - let src = loaded.src; - let home = loaded.module_id; - let src_lines: Vec<&str> = src.split('\n').collect(); - let palette = DEFAULT_PALETTE; - - // Report parsing and canonicalization problems - let alloc = RocDocAllocator::new(&src_lines, home, &loaded.interns); - - for problem in loaded.can_problems.into_iter() { - let report = can_problem(&alloc, filename.clone(), problem); - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - println!("\n{}\n", buf); - } - - for problem in loaded.type_problems.into_iter() { - let report = type_problem(&alloc, filename.clone(), problem); - let mut buf = String::new(); - - report.render_color_terminal(&mut buf, &alloc, &palette); - - println!("\n{}\n", buf); - } - - // Look up the types and expressions of the `provided` values - - // TODO instead of hardcoding this to `main`, use the `provided` list and gen all of them. - let ident_ids = loaded.interns.all_ident_ids.get(&home).unwrap(); - let main_ident_id = *ident_ids.get_id(&"main".into()).unwrap_or_else(|| { - todo!("TODO gracefully handle the case where `main` wasn't declared in the app") - }); - let main_symbol = Symbol::new(home, main_ident_id); - let mut main_var = None; - let mut main_expr = None; - - for (symbol, var) in loaded.exposed_vars_by_symbol { - if symbol == main_symbol { - main_var = Some(var); - - break; - } - } - - let mut decls_by_id = loaded.declarations_by_id; - let home_decls = decls_by_id - .remove(&loaded.module_id) - .expect("Root module ID not found in loaded declarations_by_id"); - - // We use a loop label here so we can break all the way out of a nested - // loop inside DeclareRec if we find the expr there. - // - // https://doc.rust-lang.org/1.30.0/book/first-edition/loops.html#loop-labels - 'find_expr: for decl in home_decls.iter() { - use roc_can::def::Declaration::*; - - match decl { - Declare(def) => { - if def.pattern_vars.contains_key(&main_symbol) { - main_expr = Some(def.loc_expr.clone()); - - break 'find_expr; - } - } - - DeclareRec(defs) => { - for def in defs { - if def.pattern_vars.contains_key(&main_symbol) { - main_expr = Some(def.loc_expr.clone()); - - break 'find_expr; - } - } - } - InvalidCycle(_, _) => {} - } - } - - let loc_expr = main_expr.unwrap_or_else(|| { - panic!("TODO gracefully handle the case where `main` was declared but not exposed") - }); - let mut subs = loaded.solved.into_inner(); - let content = match main_var { - Some(var) => subs.get_without_compacting(var).content, - None => todo!("TODO gracefully handle the case where `main` was declared but not exposed"), - }; - - // Generate the binary - - let context = Context::create(); - let module = module_from_builtins(&context, "app"); - let builder = context.create_builder(); - let fpm = PassManager::create(&module); - - roc_gen::llvm::build::add_passes(&fpm, opt_level); - - fpm.initialize(); - - // Compute main_fn_type before moving subs to Env - let ptr_bytes = target.pointer_width().unwrap().bytes() as u32; - let layout = Layout::new(&arena, content, &subs, ptr_bytes).unwrap_or_else(|err| { - panic!( - "Code gen error in CLI: could not convert to layout. Err was {:?}", - err - ) - }); - - let main_fn_type = - basic_type_from_layout(&arena, &context, &layout, ptr_bytes).fn_type(&[], false); - let main_fn_name = "$main"; - - // Compile and add all the Procs before adding main - let mut env = roc_gen::llvm::build::Env { - arena: &arena, - builder: &builder, - context: &context, - interns: loaded.interns, - module: arena.alloc(module), - ptr_bytes, - }; - let mut ident_ids = env.interns.all_ident_ids.remove(&home).unwrap(); - let mut layout_ids = LayoutIds::default(); - let mut procs = Procs::default(); - let mut mono_problems = std::vec::Vec::new(); - let mut mono_env = Env { - arena, - subs: &mut subs, - problems: &mut mono_problems, - home, - ident_ids: &mut ident_ids, - pointer_size: ptr_bytes, - jump_counter: arena.alloc(0), - }; - - // Add modules' decls to Procs - for (_, mut decls) in decls_by_id - .drain() - .chain(std::iter::once((loaded.module_id, home_decls))) - { - for decl in decls.drain(..) { - use roc_can::def::Declaration::*; - use roc_can::expr::Expr::*; - use roc_can::pattern::Pattern::*; - - match decl { - Declare(def) => match def.loc_pattern.value { - Identifier(symbol) => { - match def.loc_expr.value { - Closure(annotation, _, _, loc_args, boxed_body) => { - let (loc_body, ret_var) = *boxed_body; - - procs.insert_named( - &mut mono_env, - symbol, - annotation, - loc_args, - loc_body, - ret_var, - ); - } - body => { - let proc = PartialProc { - annotation: def.expr_var, - // This is a 0-arity thunk, so it has no arguments. - patterns: bumpalo::collections::Vec::new_in(arena), - body, - }; - - procs.user_defined.insert(symbol, proc); - procs.module_thunks.insert(symbol); - } - }; - } - other => { - todo!("TODO gracefully handle Declare({:?})", other); - } - }, - DeclareRec(_defs) => { - todo!("TODO support DeclareRec"); - } - InvalidCycle(_loc_idents, _regions) => { - todo!("TODO handle InvalidCycle"); - } - } - } - } - - // Populate Procs further and get the low-level Expr from the canonical Expr - let main_body = Expr::new(&mut mono_env, loc_expr.value, &mut procs); - - // Put this module's ident_ids back in the interns, so we can use them in env. - env.interns.all_ident_ids.insert(home, ident_ids); - - let mut headers = Vec::with_capacity(procs.len()); - let (mut proc_map, runtime_errors) = procs.into_map(); - - assert_eq!(runtime_errors, roc_collections::all::MutSet::default()); - - // 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, mut procs_by_layout) in proc_map.drain() { - for (layout, proc) in procs_by_layout.drain() { - let (fn_val, arg_basic_types) = - build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); - - headers.push((proc, fn_val, arg_basic_types)); - } - } - - // Build each proc using its header info. - for (proc, fn_val, arg_basic_types) in headers { - // NOTE: This is here to be uncommented in case verification fails. - // (This approach means we don't have to defensively clone name here.) - // - // println!("\n\nBuilding and then verifying function {}\n\n", name); - build_proc(&env, &mut layout_ids, proc, fn_val, arg_basic_types); - - if fn_val.verify(true) { - fpm.run_on(&fn_val); - } else { - // NOTE: If this fails, uncomment the above println to debug. - panic!( - "Non-main function failed LLVM verification. Uncomment the above println to debug!" - ); - } - } - - // Add main to the module. - let cc = get_call_conventions(target.default_calling_convention().unwrap()); - let main_fn = env.module.add_function(main_fn_name, main_fn_type, None); - - main_fn.set_call_conventions(cc); - main_fn.set_linkage(Linkage::External); - - // Add main's body - let basic_block = context.append_basic_block(main_fn, "entry"); - - builder.position_at_end(basic_block); - - let ret = roc_gen::llvm::build::build_expr( - &env, - &mut layout_ids, - &ImMap::default(), - main_fn, - &main_body, - ); - - builder.build_return(Some(&ret)); - - // 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!("Function {} failed LLVM verification.", main_fn_name); - } - - // Verify the module - if let Err(errors) = env.module.verify() { - panic!("đŸ˜± LLVM errors when defining module: {:?}", errors); - } - - // Uncomment this to see the module's optimized LLVM instruction output: - // env.module.print_to_stderr(); - - // Emit the .o file - - // NOTE: arch_str is *not* the same as the beginning of the magic target triple - // string! For example, if it's "x86-64" here, the magic target triple string - // will begin with "x86_64" (with an underscore) instead. - let arch_str = match target.architecture { - Architecture::X86_64 => { - Target::initialize_x86(&InitializationConfig::default()); - - "x86-64" - } - Architecture::Arm(_) if cfg!(feature = "target-arm") => { - // NOTE: why not enable arm and wasm by default? - // - // We had some trouble getting them to link properly. This may be resolved in the - // future, or maybe it was just some weird configuration on one machine. - Target::initialize_arm(&InitializationConfig::default()); - - "arm" - } - Architecture::Wasm32 if cfg!(feature = "target-webassembly") => { - Target::initialize_webassembly(&InitializationConfig::default()); - - "wasm32" - } - _ => panic!( - "TODO gracefully handle unsupported target architecture: {:?}", - target.architecture - ), - }; - - let opt = OptimizationLevel::Default; - let reloc = RelocMode::Default; - let model = CodeModel::Default; - - // Best guide I've found on how to determine these magic strings: - // - // https://stackoverflow.com/questions/15036909/clang-how-to-list-supported-target-architectures - // - // Clang docs are particularly helpful: http://clang.llvm.org/docs/CrossCompilation.html - let target_triple_str = match target { - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Unknown, - operating_system: OperatingSystem::Linux, - .. - } => "x86_64-unknown-linux-gnu", - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Pc, - operating_system: OperatingSystem::Linux, - .. - } => "x86_64-pc-linux-gnu", - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Unknown, - operating_system: OperatingSystem::Darwin, - .. - } => "x86_64-unknown-darwin10", - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Apple, - operating_system: OperatingSystem::Darwin, - .. - } => "x86_64-apple-darwin10", - Triple { - architecture: Architecture::X86_64, - vendor: Vendor::Pc, - operating_system: OperatingSystem::Windows, - environment: Environment::Msvc, - .. - } => "x86_64-pc-win32-gnu", - _ => panic!("TODO gracefully handle unsupported target: {:?}", target), - }; - let target_machine = Target::from_name(arch_str) - .unwrap() - .create_target_machine( - &TargetTriple::create(target_triple_str), - arch_str, - "+avx2", // TODO this string was used uncritically from an example, and should be reexamined - opt, - reloc, - model, - ) - .unwrap(); - - target_machine - .write_to_file(&env.module, FileType::Object, &dest_filename) - .expect("Writing .o file failed"); - - println!("\nSuccess!\n\n\t—> {}\n", dest_filename.display()); -} diff --git a/cli/src/repl.rs b/cli/src/repl.rs index b65f7724ac..6ac71e2c3f 100644 --- a/cli/src/repl.rs +++ b/cli/src/repl.rs @@ -20,7 +20,7 @@ use roc_gen::llvm::convert::basic_type_from_layout; use roc_module::ident::Ident; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; use roc_mono::expr::Procs; -use roc_mono::layout::Layout; +use roc_mono::layout::{Layout, LayoutCache}; use roc_parse::ast::{self, Attempting}; use roc_parse::blankspace::space0_before; use roc_parse::parser::{loc, Fail, FailReason, Parser, State}; @@ -257,29 +257,45 @@ pub fn gen(src: &str, target: Triple, opt_level: OptLevel) -> Result<(String, St pointer_size: ptr_bytes, jump_counter: arena.alloc(0), }; + let main_body = roc_mono::expr::Expr::new(&mut mono_env, loc_expr.value, &mut procs); + let mut headers = { + let num_headers = match &procs.pending_specializations { + Some(map) => map.len(), + None => 0, + }; - // Put this module's ident_ids back in the interns, so we can use them in Env. - env.interns.all_ident_ids.insert(home, ident_ids); - - let mut headers = Vec::with_capacity(procs.len()); - let (mut proc_map, runtime_errors) = procs.into_map(); + Vec::with_capacity(num_headers) + }; + let mut layout_cache = LayoutCache::default(); + let mut procs = roc_mono::expr::specialize_all(&mut mono_env, procs, &mut layout_cache); assert_eq!( - runtime_errors, - roc_collections::all::MutSet::default(), - "TODO code gen runtime exception functions" + procs.runtime_errors, + roc_collections::all::MutMap::default() ); + // 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, mut procs_by_layout) in proc_map.drain() { - for (layout, proc) in procs_by_layout.drain() { - let (fn_val, arg_basic_types) = - build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); + for ((symbol, layout), proc) in procs.specialized.drain() { + use roc_mono::expr::InProgressProc::*; - headers.push((proc, fn_val, arg_basic_types)); + match proc { + InProgress => { + panic!("A specialization was still marked InProgress after monomorphization had completed: {:?} with layout {:?}", symbol, layout); + } + Done(proc) => { + let (fn_val, arg_basic_types) = + build_proc_header(&env, &mut layout_ids, symbol, &layout, &proc); + + headers.push((proc, fn_val, arg_basic_types)); + } } } diff --git a/compiler/build/src/program.rs b/compiler/build/src/program.rs index d41b89e4d6..3c66e53113 100644 --- a/compiler/build/src/program.rs +++ b/compiler/build/src/program.rs @@ -25,7 +25,7 @@ use target_lexicon::{Architecture, OperatingSystem, Triple, Vendor}; // TODO this should probably use more helper functions // TODO make this polymorphic in the llvm functions so it can be reused for another backend. #[allow(clippy::cognitive_complexity)] -pub fn build( +pub fn gen( arena: &Bump, loaded: LoadedModule, filename: PathBuf, From d6c54645cea41e110df0f866f0712dd9848623ed Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Tue, 21 Jul 2020 02:22:50 -0400 Subject: [PATCH 61/68] cargo fmt --- cli/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 4d3bead09e..246c92ffbe 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -2,9 +2,9 @@ extern crate clap; use bumpalo::Bump; +use roc_build::program::gen; use roc_collections::all::MutMap; use roc_gen::llvm::build::OptLevel; -use roc_build::program::gen; use roc_load::file::LoadingProblem; use std::time::SystemTime; From e1fdabd5ef709e5ebfbf41e7070cf9e53f27d170 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 22 Jul 2020 19:34:14 -0400 Subject: [PATCH 62/68] Fix problem with CLI paths --- cli/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/main.rs b/cli/src/main.rs index 246c92ffbe..2fc26d1314 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -102,7 +102,7 @@ pub fn build(matches: &ArgMatches, run_after_build: bool) -> io::Result<()> { } else { OptLevel::Normal }; - let path = Path::new(filename); + let path = Path::new(filename).canonicalize().unwrap(); let src_dir = path.parent().unwrap().canonicalize().unwrap(); // Create the runtime From dd6973d75ef81786664949f1340f12ec2dc36d40 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 22 Jul 2020 19:34:34 -0400 Subject: [PATCH 63/68] Use builtin defs in modules --- compiler/can/src/module.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 38b9947d4f..a6230251b4 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -1,3 +1,4 @@ +use crate::builtins::builtin_defs; use crate::def::{canonicalize_defs, sort_can_defs, Declaration}; use crate::env::Env; use crate::expr::Output; @@ -114,7 +115,7 @@ pub fn canonicalize_module_defs<'a>( } } - let (defs, _scope, output, symbols_introduced) = canonicalize_defs( + let (mut defs, _scope, output, symbols_introduced) = canonicalize_defs( &mut env, Output::default(), var_store, @@ -152,6 +153,14 @@ pub fn canonicalize_module_defs<'a>( references.insert(*symbol); } + // Add defs for any referenced builtins. + for (symbol, def) in builtin_defs(var_store) { + if output.references.lookups.contains(&symbol) || output.references.calls.contains(&symbol) + { + defs.can_defs_by_symbol.insert(symbol, def); + } + } + match sort_can_defs(&mut env, defs, Output::default()) { (Ok(declarations), output) => { use crate::def::Declaration::*; From d9bf295d42a0a6a9eaadd361b3781c07e1466064 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 22 Jul 2020 19:34:57 -0400 Subject: [PATCH 64/68] Update hello-world README --- examples/hello-world/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/hello-world/README.md b/examples/hello-world/README.md index e230651432..521115ea8b 100644 --- a/examples/hello-world/README.md +++ b/examples/hello-world/README.md @@ -1,6 +1,6 @@ # Hello, World! -To run: +To run, `cd` into this directory and run: ```bash $ cargo run run Hello.roc @@ -42,4 +42,3 @@ Using this glue code, the Roc compiler can generate C header files describing th boundary. This not only gets us host compatibility with C compilers, but also Rust FFI for free, because [`rust-bindgen`](https://github.com/rust-lang/rust-bindgen) generates correct Rust FFI bindings from C headers. - From 40f4cd9835b11cd36d110d5035d63672bfc6edab Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 22 Jul 2020 19:37:58 -0400 Subject: [PATCH 65/68] cargo fmt --- compiler/gen/tests/gen_list.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index d9b2103224..aa494a8154 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -133,13 +133,13 @@ mod gen_list { indoc!( r#" firstList : List Int - firstList = + firstList = [] - + secondList : List Int secondList = [] - + List.append firstList secondList "# ), From 2ef37adc74383bda93c54e7d4c0c2bcad7ac6441 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 22 Jul 2020 19:38:15 -0400 Subject: [PATCH 66/68] Add some more List.append tests --- compiler/gen/tests/gen_list.rs | 63 ++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/compiler/gen/tests/gen_list.rs b/compiler/gen/tests/gen_list.rs index aa494a8154..e5d440ba89 100644 --- a/compiler/gen/tests/gen_list.rs +++ b/compiler/gen/tests/gen_list.rs @@ -163,6 +163,69 @@ mod gen_list { ); } + fn assert_append_worked(num_elems1: i64, num_elems2: i64) { + let vec1: Vec = (0..num_elems1) + .map(|i| 12345 % (i + num_elems1 + num_elems2 + 1)) + .collect(); + let vec2: Vec = (0..num_elems2) + .map(|i| 54321 % (i + num_elems1 + num_elems2 + 1)) + .collect(); + let slice_str1 = format!("{:?}", vec1); + let slice_str2 = format!("{:?}", vec2); + let mut expected = vec1; + + expected.extend(vec2); + + let expected_slice: &[i64] = expected.as_ref(); + + assert_evals_to!( + &format!("List.append {} {}", slice_str1, slice_str2), + expected_slice, + &'static [i64] + ); + } + + #[test] + fn list_append_empty_list() { + assert_append_worked(0, 0); + assert_append_worked(1, 0); + assert_append_worked(2, 0); + assert_append_worked(3, 0); + assert_append_worked(4, 0); + assert_append_worked(7, 0); + assert_append_worked(8, 0); + assert_append_worked(9, 0); + assert_append_worked(25, 0); + assert_append_worked(150, 0); + assert_append_worked(0, 1); + assert_append_worked(0, 2); + assert_append_worked(0, 3); + assert_append_worked(0, 4); + assert_append_worked(0, 7); + assert_append_worked(0, 8); + assert_append_worked(0, 9); + assert_append_worked(0, 25); + assert_append_worked(0, 150); + } + + #[test] + fn list_append_nonempty_lists() { + assert_append_worked(1, 1); + assert_append_worked(1, 2); + assert_append_worked(1, 3); + assert_append_worked(2, 3); + assert_append_worked(2, 1); + assert_append_worked(2, 2); + assert_append_worked(3, 1); + assert_append_worked(3, 2); + assert_append_worked(2, 3); + assert_append_worked(3, 3); + assert_append_worked(4, 4); + assert_append_worked(150, 150); + assert_append_worked(129, 350); + assert_append_worked(350, 129); + } + #[test] fn empty_list_len() { assert_evals_to!("List.len []", 0, usize); From 2af9854b243c81b3e0ad580fb95da987054f4f16 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 22 Jul 2020 19:38:33 -0400 Subject: [PATCH 67/68] Fix List.append off-by-one bug --- compiler/gen/src/llvm/build.rs | 344 +++++++++++++++++---------------- 1 file changed, 176 insertions(+), 168 deletions(-) diff --git a/compiler/gen/src/llvm/build.rs b/compiler/gen/src/llvm/build.rs index d385e37412..1830390078 100644 --- a/compiler/gen/src/llvm/build.rs +++ b/compiler/gen/src/llvm/build.rs @@ -2232,189 +2232,197 @@ fn list_append<'a, 'ctx, 'env>( let index_name = "#index"; let index_alloca = builder.build_alloca(ctx.i64_type(), index_name); + // The index variable begins at 0 and increments + // on each iteration of the loop + builder.build_store(index_alloca, ctx.i64_type().const_int(0, false)); // FIRST LOOP + { + let first_loop_bb = + ctx.append_basic_block(parent, "first_list_append_loop"); + builder.build_unconditional_branch(first_loop_bb); + builder.position_at_end(first_loop_bb); + + let curr_first_loop_index = builder + .build_load(index_alloca, index_name) + .into_int_value(); + + let first_list_ptr = + load_list_ptr(builder, first_list_wrapper, ptr_type); + + // The pointer to the element in the first list + let first_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + first_list_ptr, + &[curr_first_loop_index], + "load_index", + ) + }; + + // The pointer to the element in the combined list + let combined_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + combined_list_ptr, + &[curr_first_loop_index], + "load_index_combined_list", + ) + }; + + let first_list_elem = + builder.build_load(first_list_elem_ptr, "get_elem"); + + // Mutate the new array in-place to change the element. + builder.build_store(combined_list_elem_ptr, first_list_elem); + + // #index = #index + 1 + let next_first_loop_index = builder.build_int_add( + curr_first_loop_index, + ctx.i64_type().const_int(1, false), + "nextindex", + ); + + builder.build_store(index_alloca, next_first_loop_index); + + // #index < first_list_len + let first_loop_end_cond = builder.build_int_compare( + IntPredicate::ULT, + next_first_loop_index, + first_list_len, + "loopcond", + ); + + let after_first_loop_bb = + ctx.append_basic_block(parent, "after_first_loop"); + + builder.build_conditional_branch( + first_loop_end_cond, + first_loop_bb, + after_first_loop_bb, + ); + + builder.position_at_end(after_first_loop_bb); + } + + // Reset the index variable to 0 builder.build_store(index_alloca, ctx.i64_type().const_int(0, false)); - let first_loop_bb = - ctx.append_basic_block(parent, "first_list_append_loop"); - - builder.build_unconditional_branch(first_loop_bb); - builder.position_at_end(first_loop_bb); - - // #index = #index + 1 - let curr_first_loop_index = builder - .build_load(index_alloca, index_name) - .into_int_value(); - let next_first_loop_index = builder.build_int_add( - curr_first_loop_index, - ctx.i64_type().const_int(1, false), - "nextindex", - ); - - builder.build_store(index_alloca, next_first_loop_index); - - let first_list_ptr = - load_list_ptr(builder, first_list_wrapper, ptr_type); - - // The pointer to the element in the first list - let first_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - first_list_ptr, - &[curr_first_loop_index], - "load_index", - ) - }; - - // The pointer to the element in the combined list - let combined_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - combined_list_ptr, - &[curr_first_loop_index], - "load_index_combined_list", - ) - }; - - let first_list_elem = - builder.build_load(first_list_elem_ptr, "get_elem"); - - // Mutate the new array in-place to change the element. - builder.build_store(combined_list_elem_ptr, first_list_elem); - - // #index < first_list_len - let first_loop_end_cond = builder.build_int_compare( - IntPredicate::ULT, - curr_first_loop_index, - first_list_len, - "loopcond", - ); - - let after_first_loop_bb = - ctx.append_basic_block(parent, "after_first_loop"); - - builder.build_conditional_branch( - first_loop_end_cond, - first_loop_bb, - after_first_loop_bb, - ); - builder.position_at_end(after_first_loop_bb); - // SECOND LOOP - builder.build_store(index_alloca, ctx.i64_type().const_int(0, false)); + { + let second_loop_bb = + ctx.append_basic_block(parent, "second_list_append_loop"); - let second_loop_bb = - ctx.append_basic_block(parent, "second_list_append_loop"); + builder.build_unconditional_branch(second_loop_bb); + builder.position_at_end(second_loop_bb); - builder.build_unconditional_branch(second_loop_bb); - builder.position_at_end(second_loop_bb); + let curr_second_index = builder + .build_load(index_alloca, index_name) + .into_int_value(); - // #index = #index + 1 - let curr_second_index = builder - .build_load(index_alloca, index_name) - .into_int_value(); - let next_second_index = builder.build_int_add( - curr_second_index, - ctx.i64_type().const_int(1, false), - "nextindex", - ); + let second_list_ptr = + load_list_ptr(builder, second_list_wrapper, ptr_type); - builder.build_store(index_alloca, next_second_index); + // The pointer to the element in the second list + let second_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + second_list_ptr, + &[curr_second_index], + "load_index", + ) + }; - let second_list_ptr = - load_list_ptr(builder, second_list_wrapper, ptr_type); + // The pointer to the element in the combined list. + // Note that the pointer does not start at the index + // 0, it starts at the index of first_list_len. In that + // sense it is "offset". + let offset_combined_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + combined_list_ptr, + &[first_list_len], + "elem", + ) + }; - // The pointer to the element in the second list - let second_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - second_list_ptr, - &[curr_second_index], - "load_index", - ) - }; + // The pointer to the element from the second list + // in the combined list + let combined_list_elem_ptr = unsafe { + builder.build_in_bounds_gep( + offset_combined_list_elem_ptr, + &[curr_second_index], + "load_index_combined_list", + ) + }; - // The pointer to the element in the combined list. - // Note that the pointer does not start at the index - // 0, it starts at the index of first_list_len. In that - // sense it is "offset". - let offset_combined_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( + let second_list_elem = + builder.build_load(second_list_elem_ptr, "get_elem"); + + // Mutate the new array in-place to change the element. + builder.build_store(combined_list_elem_ptr, second_list_elem); + + // #index = #index + 1 + let next_second_index = builder.build_int_add( + curr_second_index, + ctx.i64_type().const_int(1, false), + "increment_index", + ); + + builder.build_store(index_alloca, next_second_index); + + // #index < second_list_len + let second_loop_end_cond = builder.build_int_compare( + IntPredicate::ULT, + next_second_index, + second_list_len, + "loopcond", + ); + + let after_second_loop_bb = + ctx.append_basic_block(parent, "after_second_loop"); + + builder.build_conditional_branch( + second_loop_end_cond, + second_loop_bb, + after_second_loop_bb, + ); + builder.position_at_end(after_second_loop_bb); + + let ptr_bytes = env.ptr_bytes; + let int_type = ptr_int(ctx, ptr_bytes); + let ptr_as_int = builder.build_ptr_to_int( combined_list_ptr, - &[first_list_len], - "elem", + int_type, + "list_cast_ptr", + ); + let struct_type = collection(ctx, ptr_bytes); + + let mut struct_val; + + // Store the pointer + struct_val = builder + .build_insert_value( + struct_type.get_undef(), + ptr_as_int, + Builtin::WRAPPER_PTR, + "insert_ptr", + ) + .unwrap(); + + // Store the length + struct_val = builder + .build_insert_value( + struct_val, + combined_list_len, + Builtin::WRAPPER_LEN, + "insert_len", + ) + .unwrap(); + + builder.build_bitcast( + struct_val.into_struct_value(), + collection(ctx, ptr_bytes), + "cast_collection", ) - }; - - // The pointer to the element from the second list - // in the combined list - let combined_list_elem_ptr = unsafe { - builder.build_in_bounds_gep( - offset_combined_list_elem_ptr, - &[curr_second_index], - "load_index_combined_list", - ) - }; - - let second_list_elem = - builder.build_load(second_list_elem_ptr, "get_elem"); - - // Mutate the new array in-place to change the element. - builder.build_store(combined_list_elem_ptr, second_list_elem); - - // #index < second_list_len - let second_loop_end_cond = builder.build_int_compare( - IntPredicate::ULT, - curr_second_index, - second_list_len, - "loopcond", - ); - - let after_second_loop_bb = - ctx.append_basic_block(parent, "after_second_loop"); - - builder.build_conditional_branch( - second_loop_end_cond, - second_loop_bb, - after_second_loop_bb, - ); - builder.position_at_end(after_second_loop_bb); - - let ptr_bytes = env.ptr_bytes; - let int_type = ptr_int(ctx, ptr_bytes); - let ptr_as_int = builder.build_ptr_to_int( - combined_list_ptr, - int_type, - "list_cast_ptr", - ); - let struct_type = collection(ctx, ptr_bytes); - - let mut struct_val; - - // Store the pointer - struct_val = builder - .build_insert_value( - struct_type.get_undef(), - ptr_as_int, - Builtin::WRAPPER_PTR, - "insert_ptr", - ) - .unwrap(); - - // Store the length - struct_val = builder - .build_insert_value( - struct_val, - combined_list_len, - Builtin::WRAPPER_LEN, - "insert_len", - ) - .unwrap(); - - builder.build_bitcast( - struct_val.into_struct_value(), - collection(ctx, ptr_bytes), - "cast_collection", - ) + } }; build_basic_phi2( From c90f2fd3285b04b125673dbd78f6bce1e5b57384 Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Wed, 22 Jul 2020 19:53:46 -0400 Subject: [PATCH 68/68] Improve error message in test_load --- compiler/load/tests/test_load.rs | 22 +++++++++++----------- compiler/load/tests/test_uniq_load.rs | 22 +++++++++++----------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/compiler/load/tests/test_load.rs b/compiler/load/tests/test_load.rs index d549378493..991daa27af 100644 --- a/compiler/load/tests/test_load.rs +++ b/compiler/load/tests/test_load.rs @@ -62,7 +62,7 @@ mod test_load { subs: &mut Subs, home: ModuleId, def: &Def, - expected_types: &HashMap<&str, &str>, + expected_types: &mut HashMap<&str, &str>, ) { for (symbol, expr_var) in &def.pattern_vars { let content = subs.get(*expr_var).content; @@ -72,34 +72,30 @@ mod test_load { let actual_str = content_to_string(content, subs, home, &interns); let fully_qualified = symbol.fully_qualified(&interns, home).to_string(); let expected_type = expected_types - .get(fully_qualified.as_str()) + .remove(fully_qualified.as_str()) .unwrap_or_else(|| { panic!("Defs included an unexpected symbol: {:?}", fully_qualified) }); - assert_eq!((&symbol, expected_type), (&symbol, &actual_str.as_str())); + assert_eq!((&symbol, expected_type), (&symbol, actual_str.as_str())); } } - fn expect_types(mut loaded_module: LoadedModule, expected_types: HashMap<&str, &str>) { + fn expect_types(mut loaded_module: LoadedModule, mut expected_types: HashMap<&str, &str>) { let home = loaded_module.module_id; let mut subs = loaded_module.solved.into_inner(); assert_eq!(loaded_module.can_problems, Vec::new()); assert_eq!(loaded_module.type_problems, Vec::new()); - let mut num_decls = 0; - for decl in loaded_module.declarations_by_id.remove(&home).unwrap() { - num_decls += 1; - match decl { Declare(def) => expect_def( &loaded_module.interns, &mut subs, home, &def, - &expected_types, + &mut expected_types, ), DeclareRec(defs) => { for def in defs { @@ -108,7 +104,7 @@ mod test_load { &mut subs, home, &def, - &expected_types, + &mut expected_types, ); } } @@ -119,7 +115,11 @@ mod test_load { }; } - assert_eq!(expected_types.len(), num_decls); + assert_eq!( + expected_types, + HashMap::default(), + "Some expected types were not found in the defs" + ); } // TESTS diff --git a/compiler/load/tests/test_uniq_load.rs b/compiler/load/tests/test_uniq_load.rs index a3b5de4750..383845d2f0 100644 --- a/compiler/load/tests/test_uniq_load.rs +++ b/compiler/load/tests/test_uniq_load.rs @@ -57,7 +57,7 @@ mod test_uniq_load { subs: &mut Subs, home: ModuleId, def: &Def, - expected_types: &HashMap<&str, &str>, + expected_types: &mut HashMap<&str, &str>, ) { for (symbol, expr_var) in &def.pattern_vars { let content = subs.get(*expr_var).content; @@ -67,34 +67,30 @@ mod test_uniq_load { let actual_str = content_to_string(content, subs, home, &interns); let fully_qualified = symbol.fully_qualified(&interns, home).to_string(); let expected_type = expected_types - .get(fully_qualified.as_str()) + .remove(fully_qualified.as_str()) .unwrap_or_else(|| { panic!("Defs included an unexpected symbol: {:?}", fully_qualified) }); - assert_eq!((&symbol, expected_type), (&symbol, &actual_str.as_str())); + assert_eq!((&symbol, expected_type), (&symbol, actual_str.as_str())); } } - fn expect_types(mut loaded_module: LoadedModule, expected_types: HashMap<&str, &str>) { + fn expect_types(mut loaded_module: LoadedModule, mut expected_types: HashMap<&str, &str>) { let home = loaded_module.module_id; let mut subs = loaded_module.solved.into_inner(); assert_eq!(loaded_module.can_problems, Vec::new()); assert_eq!(loaded_module.type_problems, Vec::new()); - let mut num_decls = 0; - for decl in loaded_module.declarations_by_id.remove(&home).unwrap() { - num_decls += 1; - match decl { Declare(def) => expect_def( &loaded_module.interns, &mut subs, home, &def, - &expected_types, + &mut expected_types, ), DeclareRec(defs) => { for def in defs { @@ -103,7 +99,7 @@ mod test_uniq_load { &mut subs, home, &def, - &expected_types, + &mut expected_types, ); } } @@ -114,7 +110,11 @@ mod test_uniq_load { }; } - assert_eq!(expected_types.len(), num_decls); + assert_eq!( + expected_types, + HashMap::default(), + "Some expected types were not found in the defs" + ); } // TESTS