diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 5b16e6285b..4a7b422402 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -1167,6 +1167,16 @@ pub fn types() -> MutMap { Box::new(list_type(flex(TVAR1))), ); + // dropIf : List elem, (elem -> Bool) -> List elem + add_top_level_function_type!( + Symbol::LIST_DROP_IF, + vec![ + list_type(flex(TVAR1)), + closure(vec![flex(TVAR1)], TVAR2, Box::new(bool_type())), + ], + Box::new(list_type(flex(TVAR1))), + ); + // swap : List elem, Nat, Nat -> List elem add_top_level_function_type!( Symbol::LIST_SWAP, diff --git a/compiler/can/src/builtins.rs b/compiler/can/src/builtins.rs index c1211427b9..654d2bf5b9 100644 --- a/compiler/can/src/builtins.rs +++ b/compiler/can/src/builtins.rs @@ -113,6 +113,7 @@ pub fn builtin_defs_map(symbol: Symbol, var_store: &mut VarStore) -> Option LIST_DROP => list_drop, LIST_DROP_AT => list_drop_at, LIST_DROP_FIRST => list_drop_first, + LIST_DROP_IF => list_drop_if, LIST_DROP_LAST => list_drop_last, LIST_SWAP => list_swap, LIST_MAP_WITH_INDEX => list_map_with_index, @@ -2482,6 +2483,7 @@ fn list_drop_at(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// List.dropFirst : List elem -> Result { first: elem, others : List elem } [ ListWasEmpty ]* fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); let index_var = var_store.fresh(); @@ -2506,6 +2508,61 @@ fn list_drop_first(symbol: Symbol, var_store: &mut VarStore) -> Def { ) } +/// List.dropIf : List elem, (elem -> Bool) -> List elem +fn list_drop_if(symbol: Symbol, var_store: &mut VarStore) -> Def { + let sym_list = Symbol::ARG_1; + let sym_predicate = Symbol::ARG_2; + let t_list = var_store.fresh(); + let t_predicate = var_store.fresh(); + let t_keep_predicate = var_store.fresh(); + let t_elem = var_store.fresh(); + + // Defer to keepIf for implementation + // List.dropIf l p = List.keepIf l (\e -> Bool.not (p e)) + + let keep_predicate = Closure(ClosureData { + function_type: t_keep_predicate, + closure_type: var_store.fresh(), + closure_ext_var: var_store.fresh(), + return_type: Variable::BOOL, + name: Symbol::LIST_DROP_IF_PREDICATE, + recursive: Recursive::NotRecursive, + captured_symbols: vec![(sym_predicate, t_predicate)], + arguments: vec![(t_elem, no_region(Pattern::Identifier(Symbol::ARG_3)))], + loc_body: { + let should_drop = Call( + Box::new(( + t_predicate, + no_region(Var(sym_predicate)), + var_store.fresh(), + Variable::BOOL, + )), + vec![(t_elem, no_region(Var(Symbol::ARG_3)))], + CalledVia::Space, + ); + Box::new(no_region(RunLowLevel { + op: LowLevel::Not, + args: vec![(Variable::BOOL, should_drop)], + ret_var: Variable::BOOL, + })) + }, + }); + + let body = RunLowLevel { + op: LowLevel::ListKeepIf, + args: vec![(t_list, Var(sym_list)), (t_keep_predicate, keep_predicate)], + ret_var: t_list, + }; + + defn( + symbol, + vec![(t_list, sym_list), (t_predicate, sym_predicate)], + var_store, + body, + t_list, + ) +} + /// List.dropLast: List elem -> List elem fn list_drop_last(symbol: Symbol, var_store: &mut VarStore) -> Def { let list_var = var_store.fresh(); diff --git a/compiler/module/src/symbol.rs b/compiler/module/src/symbol.rs index d6e75aadc6..a1f6706576 100644 --- a/compiler/module/src/symbol.rs +++ b/compiler/module/src/symbol.rs @@ -1101,6 +1101,8 @@ define_builtins! { 52 LIST_SPLIT: "split" 53 LIST_SPLIT_CLOS: "#splitClos" 54 LIST_ALL: "all" + 55 LIST_DROP_IF: "dropIf" + 56 LIST_DROP_IF_PREDICATE: "#dropIfPred" } 5 RESULT: "Result" => { 0 RESULT_RESULT: "Result" imported // the Result.Result type alias diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index cbd4eae940..6046283594 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -634,6 +634,9 @@ impl<'a> Specialized<'a> { } fn insert_specialized(&mut self, symbol: Symbol, layout: ProcLayout<'a>, proc: Proc<'a>) { + if format!("{:?}", symbol).contains("joinMapConcat") { + panic!(""); + } for (i, s) in self.symbols.iter().enumerate() { if *s == symbol && self.proc_layouts[i] == layout { match &self.procedures[i] { @@ -4294,7 +4297,20 @@ pub fn with_hole<'a>( ListKeepIf => { debug_assert_eq!(arg_symbols.len(), 2); let xs = arg_symbols[0]; - match_on_closure_argument!(ListKeepIf, [xs]) + let stmt = match_on_closure_argument!(ListKeepIf, [xs]); + + // See the comment in `walk!`. We use List.keepIf to implement + // other builtins, where the closure can be an actual closure rather + // than a symbol. + assign_to_symbol( + env, + procs, + layout_cache, + args[1].0, // the closure + Located::at_zero(args[1].1.clone()), + arg_symbols[1], + stmt, + ) } ListAny => { debug_assert_eq!(arg_symbols.len(), 2); @@ -6229,6 +6245,7 @@ fn store_record_destruct<'a>( /// for any other expression, we create a new symbol, and will /// later make sure it gets assigned the correct value. +#[derive(Debug)] enum ReuseSymbol { Imported(Symbol), LocalFunction(Symbol), diff --git a/compiler/test_gen/src/gen_list.rs b/compiler/test_gen/src/gen_list.rs index 2da869b889..44e33a8478 100644 --- a/compiler/test_gen/src/gen_list.rs +++ b/compiler/test_gen/src/gen_list.rs @@ -365,6 +365,99 @@ fn list_drop_at_shared() { ); } +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_drop_if_empty_list_of_int() { + assert_evals_to!( + indoc!( + r#" + empty : List I64 + empty = [] + + List.dropIf empty \_ -> True + "# + ), + RocList::from_slice(&[]), + RocList + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_drop_if_empty_list() { + assert_evals_to!( + indoc!( + r#" + alwaysTrue : I64 -> Bool + alwaysTrue = \_ -> True + + List.dropIf [] alwaysTrue + "# + ), + RocList::from_slice(&[]), + RocList + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_drop_if_always_false_for_non_empty_list() { + assert_evals_to!( + indoc!( + r#" + List.dropIf [1,2,3,4,5,6,7,8] (\_ -> False) + "# + ), + RocList::from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]), + RocList + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_drop_if_always_true_for_non_empty_list() { + assert_evals_to!( + indoc!( + r#" + List.dropIf [1,2,3,4,5,6,7,8] (\_ -> True) + "# + ), + RocList::from_slice(&[]), + RocList + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_drop_if_geq3() { + assert_evals_to!( + indoc!( + r#" + List.dropIf [1,2,3,4,5,6,7,8] (\n -> n >= 3) + "# + ), + RocList::from_slice(&[1, 2]), + RocList + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm"))] +fn list_drop_if_string_eq() { + assert_evals_to!( + indoc!( + r#" + List.dropIf ["x", "y", "x"] (\s -> s == "y") + "# + ), + RocList::from_slice(&[ + RocStr::from_slice("x".as_bytes()), + RocStr::from_slice("x".as_bytes()) + ]), + RocList + ); +} + #[test] #[cfg(any(feature = "gen-llvm"))] fn list_drop_last() {