mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
Merge pull request #5010 from roc-lang/tuple-record-unify
Unify IR generation for tuples and records
This commit is contained in:
commit
2f251310c0
2 changed files with 199 additions and 257 deletions
|
@ -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<roc_can::expr::Expr>),
|
||||
}
|
||||
|
||||
// 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<roc_can::expr::Expr>,
|
||||
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<Loc<roc_can::expr::Expr>>)>,
|
||||
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<roc_can::expr::Expr>),
|
||||
}
|
||||
|
||||
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, '_>,
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue