diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 01eced4c93..e867253161 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -4309,93 +4309,20 @@ pub fn with_hole<'a>( Err(_) => return runtime_error(env, "Can't create tuple with improper layout"), }; - let mut elem_symbols = Vec::with_capacity_in(elems.len(), env.arena); - let mut can_elems = Vec::with_capacity_in(elems.len(), env.arena); - - #[allow(clippy::enum_variant_names)] - enum Field { - // TODO: rename this since it can handle unspecialized expressions now too - FunctionOrUnspecialized(Symbol, Variable), - ValueSymbol, - Field(Variable, Loc), - } - // Hacky way to let us remove the owned elements from the vector, possibly out-of-order. let mut elems = Vec::from_iter_in(elems.into_iter().map(Some), env.arena); + let take_elem_expr = move |index: usize| elems[index].take(); - for (index, variable, _) in sorted_elems.into_iter() { - // TODO how should function pointers be handled here? - use ReuseSymbol::*; - let (var, loc_expr) = elems[index].take().unwrap(); - match can_reuse_symbol(env, procs, &loc_expr.value, var) { - Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => { - elem_symbols.push(symbol); - can_elems.push(Field::FunctionOrUnspecialized(symbol, variable)); - } - Value(symbol) => { - let reusable = procs.get_or_insert_symbol_specialization( - env, - layout_cache, - symbol, - var, - ); - elem_symbols.push(reusable); - can_elems.push(Field::ValueSymbol); - } - NotASymbol => { - elem_symbols.push(env.unique_symbol()); - can_elems.push(Field::Field(var, *loc_expr)); - } - } - } - - // creating a record from the var will unpack it if it's just a single field. - let layout = match layout_cache.from_var(env.arena, tuple_var, env.subs) { - Ok(layout) => layout, - Err(_) => return runtime_error(env, "Can't create record with improper layout"), - }; - - let elem_symbols = elem_symbols.into_bump_slice(); - - let mut stmt = if let [only_field] = elem_symbols { - let mut hole = hole.clone(); - substitute_in_exprs(env.arena, &mut hole, assigned, *only_field); - hole - } else { - Stmt::Let(assigned, Expr::Struct(elem_symbols), layout, hole) - }; - - for (opt_field, symbol) in can_elems.into_iter().rev().zip(elem_symbols.iter().rev()) { - match opt_field { - Field::ValueSymbol => { - // this symbol is already defined; nothing to do - } - Field::FunctionOrUnspecialized(symbol, variable) => { - stmt = specialize_symbol( - env, - procs, - layout_cache, - Some(variable), - symbol, - env.arena.alloc(stmt), - symbol, - ); - } - Field::Field(var, loc_expr) => { - stmt = with_hole( - env, - loc_expr.value, - var, - procs, - layout_cache, - *symbol, - env.arena.alloc(stmt), - ); - } - } - } - - stmt + compile_struct_like( + env, + procs, + layout_cache, + sorted_elems, + take_elem_expr, + tuple_var, + hole, + assigned, + ) } Record { @@ -4417,100 +4344,19 @@ pub fn with_hole<'a>( Err(_) => return runtime_error(env, "Can't create record with improper layout"), }; - let mut field_symbols = Vec::with_capacity_in(fields.len(), env.arena); - let mut can_fields = Vec::with_capacity_in(fields.len(), env.arena); + let take_field_expr = + move |field: Lowercase| fields.remove(&field).map(|f| (f.var, f.loc_expr)); - #[allow(clippy::enum_variant_names)] - enum Field { - // TODO: rename this since it can handle unspecialized expressions now too - FunctionOrUnspecialized(Symbol, Variable), - ValueSymbol, - Field(roc_can::expr::Field), - } - - for (label, variable, _) in sorted_fields.into_iter() { - // TODO how should function pointers be handled here? - use ReuseSymbol::*; - match fields.remove(&label) { - Some(field) => { - match can_reuse_symbol(env, procs, &field.loc_expr.value, field.var) { - Imported(symbol) - | LocalFunction(symbol) - | UnspecializedExpr(symbol) => { - field_symbols.push(symbol); - can_fields.push(Field::FunctionOrUnspecialized(symbol, variable)); - } - Value(symbol) => { - let reusable = procs.get_or_insert_symbol_specialization( - env, - layout_cache, - symbol, - field.var, - ); - field_symbols.push(reusable); - can_fields.push(Field::ValueSymbol); - } - NotASymbol => { - field_symbols.push(env.unique_symbol()); - can_fields.push(Field::Field(field)); - } - } - } - None => { - // this field was optional, but not given - continue; - } - } - } - - // creating a record from the var will unpack it if it's just a single field. - let layout = match layout_cache.from_var(env.arena, record_var, env.subs) { - Ok(layout) => layout, - Err(_) => return runtime_error(env, "Can't create record with improper layout"), - }; - - let field_symbols = field_symbols.into_bump_slice(); - - let mut stmt = if let [only_field] = field_symbols { - let mut hole = hole.clone(); - substitute_in_exprs(env.arena, &mut hole, assigned, *only_field); - hole - } else { - Stmt::Let(assigned, Expr::Struct(field_symbols), layout, hole) - }; - - for (opt_field, symbol) in can_fields.into_iter().rev().zip(field_symbols.iter().rev()) - { - match opt_field { - Field::ValueSymbol => { - // this symbol is already defined; nothing to do - } - Field::FunctionOrUnspecialized(symbol, variable) => { - stmt = specialize_symbol( - env, - procs, - layout_cache, - Some(variable), - symbol, - env.arena.alloc(stmt), - symbol, - ); - } - Field::Field(field) => { - stmt = with_hole( - env, - field.loc_expr.value, - field.var, - procs, - layout_cache, - *symbol, - env.arena.alloc(stmt), - ); - } - } - } - - stmt + compile_struct_like( + env, + procs, + layout_cache, + sorted_fields, + take_field_expr, + record_var, + hole, + assigned, + ) } EmptyRecord => let_empty_struct(assigned, hole), @@ -4830,49 +4676,18 @@ pub fn with_hole<'a>( } } - let record_symbol = possible_reuse_symbol_or_specialize( + compile_struct_like_access( env, procs, layout_cache, - &loc_expr.value, - record_var, - ); - - let mut stmt = match field_layouts.as_slice() { - [_] => { - let mut hole = hole.clone(); - substitute_in_exprs(env.arena, &mut hole, assigned, record_symbol); - - hole - } - _ => { - let expr = Expr::StructAtIndex { - index: index.expect("field not in its own type") as u64, - field_layouts: field_layouts.into_bump_slice(), - structure: record_symbol, - }; - - let layout = layout_cache - .from_var(env.arena, field_var, env.subs) - .unwrap_or_else(|err| { - panic!("TODO turn fn_var into a RuntimeError {:?}", err) - }); - - Stmt::Let(assigned, expr, layout, hole) - } - }; - - stmt = assign_to_symbol( - env, - procs, - layout_cache, - record_var, + field_layouts, + index.expect("field not in its own type") as _, *loc_expr, - record_symbol, - stmt, - ); - - stmt + record_var, + hole, + assigned, + field_var, + ) } RecordAccessor(accessor_data) => { @@ -4950,61 +4765,30 @@ pub fn with_hole<'a>( Ok(fields) => fields, Err(_) => return runtime_error(env, "Can't access tuple with improper layout"), }; + let mut field_layouts = Vec::with_capacity_in(sorted_elems.len(), env.arena); let mut final_index = None; - let mut elem_layouts = Vec::with_capacity_in(sorted_elems.len(), env.arena); for (current, (index, _, elem_layout)) in sorted_elems.into_iter().enumerate() { - elem_layouts.push(elem_layout); + field_layouts.push(elem_layout); if index == accessed_index { final_index = Some(current); } } - let tuple_symbol = possible_reuse_symbol_or_specialize( + compile_struct_like_access( env, procs, layout_cache, - &loc_expr.value, - tuple_var, - ); - - let mut stmt = match elem_layouts.as_slice() { - [_] => { - let mut hole = hole.clone(); - substitute_in_exprs(env.arena, &mut hole, assigned, tuple_symbol); - - hole - } - _ => { - let expr = Expr::StructAtIndex { - index: final_index.expect("field not in its own type") as u64, - field_layouts: elem_layouts.into_bump_slice(), - structure: tuple_symbol, - }; - - let layout = layout_cache - .from_var(env.arena, elem_var, env.subs) - .unwrap_or_else(|err| { - panic!("TODO turn fn_var into a RuntimeError {:?}", err) - }); - - Stmt::Let(assigned, expr, layout, hole) - } - }; - - stmt = assign_to_symbol( - env, - procs, - layout_cache, - tuple_var, + field_layouts, + final_index.expect("elem not in its own type") as u64, *loc_expr, - tuple_symbol, - stmt, - ); - - stmt + tuple_var, + hole, + assigned, + elem_var, + ) } OpaqueWrapFunction(wrap_fn_data) => { @@ -5762,6 +5546,162 @@ pub fn with_hole<'a>( } } +/// Compiles an access into a tuple or record. +fn compile_struct_like_access<'a>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + field_layouts: Vec<'a, InLayout<'a>>, + index: u64, + loc_expr: Loc, + struct_like_var: Variable, + hole: &'a Stmt<'a>, + assigned: Symbol, + elem_var: Variable, +) -> Stmt<'a> { + let struct_symbol = possible_reuse_symbol_or_specialize( + env, + procs, + layout_cache, + &loc_expr.value, + struct_like_var, + ); + + let mut stmt = match field_layouts.as_slice() { + [_] => { + let mut hole = hole.clone(); + substitute_in_exprs(env.arena, &mut hole, assigned, struct_symbol); + + hole + } + _ => { + let expr = Expr::StructAtIndex { + index, + field_layouts: field_layouts.into_bump_slice(), + structure: struct_symbol, + }; + + let layout = layout_cache + .from_var(env.arena, elem_var, env.subs) + .unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err)); + + Stmt::Let(assigned, expr, layout, hole) + } + }; + + stmt = assign_to_symbol( + env, + procs, + layout_cache, + struct_like_var, + loc_expr, + struct_symbol, + stmt, + ); + + stmt +} + +/// Compiles a record or a tuple. +// TODO: UnusedLayout is because `sort_record_fields` currently returns a three-tuple, but is, in +// fact, unneeded for the compilation. +fn compile_struct_like<'a, L, UnusedLayout>( + env: &mut Env<'a, '_>, + procs: &mut Procs<'a>, + layout_cache: &mut LayoutCache<'a>, + sorted_elems: Vec<(L, Variable, UnusedLayout)>, + mut take_elem_expr: impl FnMut(L) -> Option<(Variable, Box>)>, + struct_like_var: Variable, + hole: &'a Stmt<'a>, + assigned: Symbol, +) -> Stmt<'a> { + let mut elem_symbols = Vec::with_capacity_in(sorted_elems.len(), env.arena); + let mut can_elems = Vec::with_capacity_in(sorted_elems.len(), env.arena); + + #[allow(clippy::enum_variant_names)] + enum Field { + // TODO: rename this since it can handle unspecialized expressions now too + FunctionOrUnspecialized(Symbol, Variable), + ValueSymbol, + Field(Variable, Loc), + } + + for (index, variable, _) in sorted_elems.into_iter() { + // TODO how should function pointers be handled here? + use ReuseSymbol::*; + match take_elem_expr(index) { + Some((var, loc_expr)) => match can_reuse_symbol(env, procs, &loc_expr.value, var) { + Imported(symbol) | LocalFunction(symbol) | UnspecializedExpr(symbol) => { + elem_symbols.push(symbol); + can_elems.push(Field::FunctionOrUnspecialized(symbol, variable)); + } + Value(symbol) => { + let reusable = + procs.get_or_insert_symbol_specialization(env, layout_cache, symbol, var); + elem_symbols.push(reusable); + can_elems.push(Field::ValueSymbol); + } + NotASymbol => { + elem_symbols.push(env.unique_symbol()); + can_elems.push(Field::Field(var, *loc_expr)); + } + }, + None => { + // this field was optional, but not given + continue; + } + } + } + + // creating a record from the var will unpack it if it's just a single field. + let layout = match layout_cache.from_var(env.arena, struct_like_var, env.subs) { + Ok(layout) => layout, + Err(_) => return runtime_error(env, "Can't create record with improper layout"), + }; + + let elem_symbols = elem_symbols.into_bump_slice(); + + let mut stmt = if let [only_field] = elem_symbols { + let mut hole = hole.clone(); + substitute_in_exprs(env.arena, &mut hole, assigned, *only_field); + hole + } else { + Stmt::Let(assigned, Expr::Struct(elem_symbols), layout, hole) + }; + + for (opt_field, symbol) in can_elems.into_iter().rev().zip(elem_symbols.iter().rev()) { + match opt_field { + Field::ValueSymbol => { + // this symbol is already defined; nothing to do + } + Field::FunctionOrUnspecialized(symbol, variable) => { + stmt = specialize_symbol( + env, + procs, + layout_cache, + Some(variable), + symbol, + env.arena.alloc(stmt), + symbol, + ); + } + Field::Field(var, loc_expr) => { + stmt = with_hole( + env, + loc_expr.value, + var, + procs, + layout_cache, + *symbol, + env.arena.alloc(stmt), + ); + } + } + } + + stmt +} + #[inline(always)] fn late_resolve_ability_specialization<'a>( env: &mut Env<'a, '_>, diff --git a/crates/compiler/mono/src/lib.rs b/crates/compiler/mono/src/lib.rs index ea50d61bfe..d82c7a228a 100644 --- a/crates/compiler/mono/src/lib.rs +++ b/crates/compiler/mono/src/lib.rs @@ -6,6 +6,8 @@ #![warn(clippy::dbg_macro)] // See github.com/roc-lang/roc/issues/800 for discussion of the large_enum_variant check. #![allow(clippy::large_enum_variant, clippy::upper_case_acronyms)] +// Not a useful lint for us +#![allow(clippy::too_many_arguments)] pub mod borrow; pub mod code_gen_help;