mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
Improve representation of record fields in mono
This commit is contained in:
parent
073df686bc
commit
13efa083dd
6 changed files with 459 additions and 307 deletions
|
@ -17,4 +17,4 @@ pub use mono_module::{InternedStrId, Interns};
|
|||
pub use mono_num::Number;
|
||||
pub use mono_struct::MonoFieldId;
|
||||
pub use mono_type::{MonoType, MonoTypeId, MonoTypes};
|
||||
pub use specialize_type::{MonoCache, Problem, RecordFieldIds, TupleElemIds};
|
||||
pub use specialize_type::{MonoTypeCache, Problem, RecordFieldIds, TupleElemIds};
|
||||
|
|
|
@ -1,22 +1,52 @@
|
|||
use crate::{
|
||||
mono_ir::{MonoExpr, MonoExprId, MonoExprs},
|
||||
mono_ir::{MonoExpr, MonoExprId, MonoExprs, MonoStmt, MonoStmtId},
|
||||
mono_module::Interns,
|
||||
mono_num::Number,
|
||||
mono_type::{MonoType, MonoTypes, Primitive},
|
||||
specialize_type::{MonoCache, Problem, RecordFieldIds, TupleElemIds},
|
||||
DebugInfo,
|
||||
specialize_type::{MonoTypeCache, Problem, RecordFieldIds, TupleElemIds},
|
||||
DebugInfo, MonoTypeId,
|
||||
};
|
||||
use bumpalo::{collections::Vec, Bump};
|
||||
use roc_can::expr::{Expr, IntValue};
|
||||
use roc_collections::Push;
|
||||
use roc_collections::{Push, VecMap};
|
||||
use roc_module::{ident::Lowercase, symbol::ModuleId};
|
||||
use roc_region::all::Region;
|
||||
use roc_solve::module::Solved;
|
||||
use roc_types::subs::{Content, Subs};
|
||||
use soa::{Index, NonEmptySlice, Slice};
|
||||
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
||||
use soa::NonEmptySlice;
|
||||
|
||||
/// Function bodies that have already been specialized.
|
||||
pub struct MonoFnCache {
|
||||
inner: VecMap<(ModuleId, Variable, MonoTypeId), MonoExprId>,
|
||||
}
|
||||
|
||||
impl MonoFnCache {
|
||||
pub fn monomorphize_fn<'a, F: 'a + FnOnce(ModuleId, Variable) -> &'a Expr>(
|
||||
&mut self,
|
||||
// Sometimes we need to create specializations of functions that are defined in other modules.
|
||||
module_id: ModuleId,
|
||||
// The function Variable stored in the original function's canonical Expr. We use this as a way to
|
||||
// uniquely identify the function expr within its module, since each fn Expr gets its own unique var.
|
||||
// Doing it with Variable instead of IdentId lets us cache specializations of anonymous functions too.
|
||||
fn_var: Variable,
|
||||
// Given a ModuleId and Variable (to uniquely identify the canonical fn Expr within its module),
|
||||
// get the canonical Expr of the function itself. We need this to create a specialization of it.
|
||||
get_fn_expr: F,
|
||||
// This tells us which specialization of the function we want.
|
||||
mono_type_id: MonoTypeId,
|
||||
) -> MonoExprId {
|
||||
*self
|
||||
.inner
|
||||
.get_or_insert((module_id, fn_var, mono_type_id), || {
|
||||
todo!("TODO lower the fn_expr using Env etc. (May need to add args to this method, not sure.)");
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Env<'a, 'c, 'd, 'i, 's, 't, P> {
|
||||
arena: &'a Bump,
|
||||
subs: &'s mut Subs,
|
||||
types_cache: &'c mut MonoCache,
|
||||
types_cache: &'c mut MonoTypeCache,
|
||||
mono_types: &'t mut MonoTypes,
|
||||
mono_exprs: &'t mut MonoExprs,
|
||||
record_field_ids: RecordFieldIds,
|
||||
|
@ -30,7 +60,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
|
|||
pub fn new(
|
||||
arena: &'a Bump,
|
||||
subs: &'s mut Solved<Subs>,
|
||||
types_cache: &'c mut MonoCache,
|
||||
types_cache: &'c mut MonoTypeCache,
|
||||
mono_types: &'t mut MonoTypes,
|
||||
mono_exprs: &'t mut MonoExprs,
|
||||
record_field_ids: RecordFieldIds,
|
||||
|
@ -58,6 +88,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
|
|||
let mono_types = &mut self.mono_types;
|
||||
let mut mono_from_var = |var| {
|
||||
self.types_cache.monomorphize_var(
|
||||
self.arena,
|
||||
self.subs,
|
||||
mono_types,
|
||||
&mut self.record_field_ids,
|
||||
|
@ -151,59 +182,116 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
|
|||
|
||||
fields.sort_by(|(name1, _), (name2, _)| name1.cmp(name2));
|
||||
|
||||
// Reserve a slice of IDs up front. This is so that we have a contiguous array
|
||||
// of field IDs at the end of this, each corresponding to the appropriate record field.
|
||||
let field_ids: Slice<MonoExprId> = self.mono_exprs.reserve_ids(fields.len() as u16);
|
||||
let mut next_field_id = field_ids.start();
|
||||
// We want to end up with a Slice<MonoExpr> of these, so accumulate a buffer of them
|
||||
// and then add them to MonoExprs all at once, so they're added as one contiguous slice
|
||||
// regardless of what else got added to MonoExprs as we were monomorphizing them.
|
||||
let mut buf: Vec<(MonoExpr, Region)> =
|
||||
Vec::with_capacity_in(fields.len(), self.arena);
|
||||
|
||||
// Generate a MonoExpr for each field, using the reserved IDs so that we end up with
|
||||
// that Slice being populated with the exprs in the fields, with the correct ordering.
|
||||
fields.retain(|(_name, field)| {
|
||||
match self.to_mono_expr(&field.loc_expr.value) {
|
||||
Some(mono_expr) => {
|
||||
// Safety: This will run *at most* field.len() times, possibly less,
|
||||
// so this will never create an index that's out of bounds.
|
||||
let mono_expr_id =
|
||||
unsafe { MonoExprId::new_unchecked(Index::new(next_field_id)) };
|
||||
buf.extend(
|
||||
// flat_map these so we discard all the fields that monomorphized to None
|
||||
fields.into_iter().flat_map(|(_name, field)| {
|
||||
self.to_mono_expr(&field.loc_expr.value)
|
||||
.map(|mono_expr| (mono_expr, field.loc_expr.region))
|
||||
}),
|
||||
);
|
||||
|
||||
next_field_id += 1;
|
||||
|
||||
self.mono_exprs
|
||||
.insert(mono_expr_id, mono_expr, field.loc_expr.region);
|
||||
|
||||
true
|
||||
}
|
||||
None => {
|
||||
// Discard all the zero-sized fields as we go.
|
||||
false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Check for zero-sized and single-field records again now that we've discarded zero-sized fields,
|
||||
// because we might have ended up with 0 or 1 remaining fields.
|
||||
if fields.len() > 1 {
|
||||
// Safety: We just verified that there's more than 1 field.
|
||||
unsafe {
|
||||
Some(MonoExpr::Struct(NonEmptySlice::new_unchecked(
|
||||
field_ids.start,
|
||||
fields.len() as u16,
|
||||
)))
|
||||
}
|
||||
} else {
|
||||
// If there are 0 fields remaining, return None. If there's 1, unwrap it.
|
||||
fields
|
||||
.first()
|
||||
.and_then(|(_, field)| self.to_mono_expr(&field.loc_expr.value))
|
||||
// If we ended up with exactly 1 field, return it unwrapped.
|
||||
if buf.len() == 1 {
|
||||
return buf.pop().map(|(expr, _region)| expr);
|
||||
}
|
||||
|
||||
NonEmptySlice::from_slice(self.mono_exprs.extend(buf.iter().copied()))
|
||||
.map(MonoExpr::Struct)
|
||||
}
|
||||
// Expr::Call((fn_var, fn_expr, capture_var, ret_var), args, called_via) => {
|
||||
// let opt_ret_type = mono_from_var(*var);
|
||||
|
||||
// if opt_ret_type.is_none() {
|
||||
// let fn_type = match self.subs.get_content_without_compacting(fn_var) {
|
||||
// Content::Structure(FlatType::Func(arg_vars, closure_var, ret_var)) => {
|
||||
// let todo = (); // TODO make is_effectful actually use the function's effectfulness!
|
||||
// let is_effectful = false;
|
||||
|
||||
// // Calls to pure functions that return zero-sized types should be discarded.
|
||||
// if !is_effectful {
|
||||
// return None;
|
||||
// }
|
||||
|
||||
// // Use the Content we already have to directly monomorphize the function, rather than
|
||||
// // calling monomorphize_var and having it redo the Subs lookup and conditionals we just did.
|
||||
// self.types_cache.monomorphize_fn(
|
||||
// self.subs,
|
||||
// self.mono_types,
|
||||
// &mut self.record_field_ids,
|
||||
// &mut self.tuple_elem_ids,
|
||||
// &mut self.problems,
|
||||
// self.debug_info,
|
||||
// *arg_vars,
|
||||
// *ret_var,
|
||||
// )?
|
||||
// }
|
||||
// _ => {
|
||||
// // This function didn't have a function type. Compiler bug!
|
||||
// return Some(MonoExpr::CompilerBug(Problem::FnDidNotHaveFnType));
|
||||
// }
|
||||
// };
|
||||
|
||||
// let todo = (); // TODO this is where we need to specialize, which means...duplicating the fn expr body maybe? and caching it under the mono type?
|
||||
// let fn_expr = self.to_mono_expr(can_expr, stmts)?;
|
||||
// let args = todo!(); // TODO compute the args. This is tricky because of preallocated slices!
|
||||
// let capture_type = mono_from_var(*capture_var);
|
||||
|
||||
// let todo = (); // How do we pre-reserve the statements? Is that possible? It does seem necessary...might not be possible though. Maybe we just need to make Vec rather than Slice on these.
|
||||
|
||||
// // We aren't returning anything, and this is an effectful function, so just push a statement to call it and move on.
|
||||
// stmts.push(self.mono_stmts.add(MonoStmt::CallVoid {
|
||||
// fn_type,
|
||||
// fn_expr,
|
||||
// args,
|
||||
// capture_type,
|
||||
// }));
|
||||
|
||||
// None
|
||||
// } else {
|
||||
// let fn_type = mono_from_var(*fn_var)?;
|
||||
// let todo = (); // TODO this is where we need to specialize, which means...duplicating the fn expr body maybe? and caching it under the mono type?
|
||||
// let fn_expr = self.to_mono_expr(can_expr, stmts)?;
|
||||
// let args = todo!(); // TODO compute the args. This is tricky because of preallocated slices!
|
||||
// let capture_type = mono_from_var(*capture_var);
|
||||
|
||||
// Some(MonoExpr::Call {
|
||||
// fn_type,
|
||||
// fn_expr,
|
||||
// args,
|
||||
// capture_type,
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
// Expr::Var(symbol, var) => Some(MonoExpr::Lookup(*symbol, mono_from_var(*var)?)),
|
||||
// Expr::LetNonRec(def, loc) => {
|
||||
// let expr = self.to_mono_expr(def.loc_expr.value, stmts)?;
|
||||
// let todo = (); // TODO if this is an underscore pattern and we're doing a fn call, convert it to Stmt::CallVoid
|
||||
// let pattern = self.to_mono_pattern(def.loc_pattern.value);
|
||||
|
||||
// // TODO do we need to use any of these other fields? e.g. for the types?
|
||||
// // pub struct Def {
|
||||
// // pub loc_pattern: Loc<Pattern>,
|
||||
// // pub loc_expr: Loc<Expr>,
|
||||
// // pub expr_var: Variable,
|
||||
// // pub pattern_vars: SendMap<Symbol, Variable>,
|
||||
// // pub annotation: Option<Annotation>,
|
||||
// // }
|
||||
|
||||
// todo!("split up the pattern into various Assign statements.");
|
||||
// }
|
||||
// Expr::LetRec(vec, loc, illegal_cycle_mark) => todo!(),
|
||||
_ => todo!(),
|
||||
// Expr::List {
|
||||
// elem_var,
|
||||
// loc_elems,
|
||||
// } => todo!(),
|
||||
// Expr::IngestedFile(path_buf, arc, variable) => todo!(),
|
||||
// Expr::Var(symbol, variable) => todo!(),
|
||||
// Expr::ParamsVar {
|
||||
// symbol,
|
||||
// var,
|
||||
|
@ -226,8 +314,6 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
|
|||
// branches,
|
||||
// final_else,
|
||||
// } => todo!(),
|
||||
// Expr::LetRec(vec, loc, illegal_cycle_mark) => todo!(),
|
||||
// Expr::LetNonRec(def, loc) => todo!(),
|
||||
// Expr::Call(_, vec, called_via) => todo!(),
|
||||
// Expr::RunLowLevel { op, args, ret_var } => todo!(),
|
||||
// Expr::ForeignCall {
|
||||
|
|
|
@ -117,8 +117,8 @@ impl MonoExprs {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn iter_slice(&self, expr_ids: Slice<MonoExprId>) -> impl Iterator<Item = &MonoExpr> {
|
||||
expr_ids.indices().into_iter().map(|index| {
|
||||
pub fn iter_slice(&self, exprs: Slice<MonoExpr>) -> impl Iterator<Item = &MonoExpr> {
|
||||
exprs.indices().into_iter().map(|index| {
|
||||
debug_assert!(
|
||||
self.exprs.get(index).is_some(),
|
||||
"A Slice index was not found in MonoExprs. This should never happen!"
|
||||
|
@ -128,6 +128,20 @@ impl MonoExprs {
|
|||
unsafe { self.exprs.get_unchecked(index) }
|
||||
})
|
||||
}
|
||||
|
||||
pub fn extend(
|
||||
&mut self,
|
||||
exprs: impl Iterator<Item = (MonoExpr, Region)> + Clone,
|
||||
) -> Slice<MonoExpr> {
|
||||
let start = self.exprs.len();
|
||||
|
||||
self.exprs.extend(exprs.clone().map(|(expr, _region)| expr));
|
||||
self.regions.extend(exprs.map(|(_expr, region)| region));
|
||||
|
||||
let len = self.exprs.len() - start;
|
||||
|
||||
Slice::new(start as u32, len as u16)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
|
@ -141,6 +155,82 @@ impl MonoExprId {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct MonoStmtId {
|
||||
inner: Id<MonoStmt>,
|
||||
}
|
||||
|
||||
impl MonoStmtId {
|
||||
pub(crate) unsafe fn new_unchecked(inner: Id<MonoStmt>) -> Self {
|
||||
Self { inner }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum MonoStmt {
|
||||
/// Assign to a variable.
|
||||
Assign(IdentId, MonoExprId),
|
||||
AssignRec(IdentId, MonoExprId),
|
||||
|
||||
/// Introduce a variable, e.g. `var foo_` (we'll MonoStmt::Assign to it later.)
|
||||
Declare(IdentId),
|
||||
|
||||
/// The `return` statement
|
||||
Return(MonoExprId),
|
||||
|
||||
/// The "crash" keyword. Importantly, during code gen we must mark this as "nothing happens after this"
|
||||
Crash {
|
||||
msg: MonoExprId,
|
||||
/// The type of the `crash` expression (which will have unified to whatever's around it)
|
||||
expr_type: MonoTypeId,
|
||||
},
|
||||
|
||||
Expect {
|
||||
condition: MonoExprId,
|
||||
/// If the expectation fails, we print the values of all the named variables
|
||||
/// in the final expr. These are those values.
|
||||
lookups_in_cond: Slice2<MonoTypeId, IdentId>,
|
||||
},
|
||||
|
||||
Dbg {
|
||||
source_location: InternedStrId,
|
||||
source: InternedStrId,
|
||||
expr: MonoExprId,
|
||||
expr_type: MonoTypeId,
|
||||
name: IdentId,
|
||||
},
|
||||
|
||||
// Call a function that has no return value (or which we are discarding due to an underscore pattern).
|
||||
CallVoid {
|
||||
fn_type: MonoTypeId,
|
||||
fn_expr: MonoExprId,
|
||||
args: Slice2<MonoTypeId, MonoExprId>,
|
||||
/// This is the type of the closure based only on canonical IR info,
|
||||
/// not considering what other closures might later influence it.
|
||||
/// Lambda set specialization may change this type later!
|
||||
capture_type: MonoTypeId,
|
||||
},
|
||||
|
||||
// Branching
|
||||
When {
|
||||
/// The actual condition of the when expression.
|
||||
cond: MonoExprId,
|
||||
cond_type: MonoTypeId,
|
||||
/// Type of each branch (and therefore the type of the entire `when` expression)
|
||||
branch_type: MonoTypeId,
|
||||
/// Note: if the branches weren't exhaustive, we will have already generated a default
|
||||
/// branch which crashes if it's reached. (The compiler will have reported an error already;
|
||||
/// this is for if you want to run anyway.)
|
||||
branches: NonEmptySlice<WhenBranch>,
|
||||
},
|
||||
If {
|
||||
/// Type of each branch (and therefore the type of the entire `if` expression)
|
||||
branch_type: MonoTypeId,
|
||||
branches: Slice<(MonoStmtId, MonoStmtId)>,
|
||||
final_else: Option<MonoTypeId>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub enum MonoExpr {
|
||||
Str(InternedStrId),
|
||||
|
@ -159,35 +249,6 @@ pub enum MonoExpr {
|
|||
params_type: MonoTypeId,
|
||||
},
|
||||
|
||||
// Branching
|
||||
When {
|
||||
/// The actual condition of the when expression.
|
||||
cond: MonoExprId,
|
||||
cond_type: MonoTypeId,
|
||||
/// Type of each branch (and therefore the type of the entire `when` expression)
|
||||
branch_type: MonoTypeId,
|
||||
/// Note: if the branches weren't exhaustive, we will have already generated a default
|
||||
/// branch which crashes if it's reached. (The compiler will have reported an error already;
|
||||
/// this is for if you want to run anyway.)
|
||||
branches: NonEmptySlice<WhenBranch>,
|
||||
},
|
||||
If {
|
||||
/// Type of each branch (and therefore the type of the entire `if` expression)
|
||||
branch_type: MonoTypeId,
|
||||
branches: Slice<(MonoExprId, MonoExprId)>,
|
||||
final_else: Option<MonoTypeId>,
|
||||
},
|
||||
|
||||
// Let
|
||||
LetRec {
|
||||
defs: Slice<Def>,
|
||||
ending_expr: MonoExprId,
|
||||
},
|
||||
LetNonRec {
|
||||
def: Def,
|
||||
ending_expr: MonoExprId,
|
||||
},
|
||||
|
||||
/// This is *only* for calling functions, not for tag application.
|
||||
/// The Tag variant contains any applied values inside it.
|
||||
Call {
|
||||
|
@ -197,7 +258,7 @@ pub enum MonoExpr {
|
|||
/// This is the type of the closure based only on canonical IR info,
|
||||
/// not considering what other closures might later influence it.
|
||||
/// Lambda set specialization may change this type later!
|
||||
closure_type: MonoTypeId,
|
||||
capture_type: MonoTypeId,
|
||||
},
|
||||
RunLowLevel {
|
||||
op: LowLevel,
|
||||
|
@ -220,14 +281,7 @@ pub enum MonoExpr {
|
|||
|
||||
/// A record literal or a tuple literal.
|
||||
/// These have already been sorted alphabetically.
|
||||
Struct(NonEmptySlice<MonoExprId>),
|
||||
|
||||
/// The "crash" keyword. Importantly, during code gen we must mark this as "nothing happens after this"
|
||||
Crash {
|
||||
msg: MonoExprId,
|
||||
/// The type of the `crash` expression (which will have unified to whatever's around it)
|
||||
expr_type: MonoTypeId,
|
||||
},
|
||||
Struct(NonEmptySlice<MonoExpr>),
|
||||
|
||||
/// Look up exactly one field on a record, tuple, or tag payload.
|
||||
/// At this point we've already unified those concepts and have
|
||||
|
@ -264,28 +318,18 @@ pub enum MonoExpr {
|
|||
args: Slice2<MonoTypeId, MonoExprId>,
|
||||
},
|
||||
|
||||
Expect {
|
||||
condition: MonoExprId,
|
||||
continuation: MonoExprId,
|
||||
/// If the expectation fails, we print the values of all the named variables
|
||||
/// in the final expr. These are those values.
|
||||
lookups_in_cond: Slice2<MonoTypeId, IdentId>,
|
||||
},
|
||||
Dbg {
|
||||
source_location: InternedStrId,
|
||||
source: InternedStrId,
|
||||
msg: MonoExprId,
|
||||
continuation: MonoExprId,
|
||||
expr_type: MonoTypeId,
|
||||
name: IdentId,
|
||||
Block {
|
||||
stmts: Slice<MonoStmtId>,
|
||||
final_expr: MonoExprId,
|
||||
},
|
||||
|
||||
CompilerBug(Problem),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct WhenBranch {
|
||||
pub patterns: Slice<MonoPatternId>,
|
||||
pub body: MonoExprId,
|
||||
pub body: Slice<MonoStmtId>,
|
||||
pub guard: Option<MonoExprId>,
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ use crate::{
|
|||
mono_type::{MonoTypeId, MonoTypes},
|
||||
MonoFieldId, MonoType,
|
||||
};
|
||||
use bumpalo::{collections::Vec, Bump};
|
||||
use roc_collections::{Push, VecMap};
|
||||
use roc_module::{ident::Lowercase, symbol::Symbol};
|
||||
use roc_solve::module::Solved;
|
||||
|
@ -33,6 +34,7 @@ pub enum Problem {
|
|||
),
|
||||
BadNumTypeParam,
|
||||
UninitializedReservedExpr,
|
||||
FnDidNotHaveFnType,
|
||||
}
|
||||
|
||||
/// For MonoTypes that are records, store their field indices.
|
||||
|
@ -46,11 +48,11 @@ pub type RecordFieldIds = VecMap<MonoTypeId, VecMap<Lowercase, MonoFieldId>>;
|
|||
pub type TupleElemIds = VecMap<MonoTypeId, VecMap<u16, MonoFieldId>>;
|
||||
|
||||
/// Variables that have already been monomorphized.
|
||||
pub struct MonoCache {
|
||||
pub struct MonoTypeCache {
|
||||
inner: VecMap<Variable, MonoTypeId>,
|
||||
}
|
||||
|
||||
impl MonoCache {
|
||||
impl MonoTypeCache {
|
||||
pub fn from_solved_subs(subs: &Solved<Subs>) -> Self {
|
||||
Self {
|
||||
inner: VecMap::with_capacity(subs.inner().len()),
|
||||
|
@ -61,6 +63,7 @@ impl MonoCache {
|
|||
/// (e.g. a zero-sized type like empty record, empty tuple, a record of just those, etc.)
|
||||
pub fn monomorphize_var(
|
||||
&mut self,
|
||||
arena: &Bump,
|
||||
subs: &Subs,
|
||||
mono_types: &mut MonoTypes,
|
||||
field_indices: &mut RecordFieldIds,
|
||||
|
@ -70,6 +73,7 @@ impl MonoCache {
|
|||
var: Variable,
|
||||
) -> Option<MonoTypeId> {
|
||||
let mut env = Env {
|
||||
arena,
|
||||
cache: self,
|
||||
mono_types,
|
||||
field_ids: field_indices,
|
||||
|
@ -78,12 +82,13 @@ impl MonoCache {
|
|||
debug_info,
|
||||
};
|
||||
|
||||
lower_var(&mut env, subs, var)
|
||||
env.lower_var(subs, var)
|
||||
}
|
||||
}
|
||||
|
||||
struct Env<'c, 'd, 'e, 'f, 'm, 'p, P: Push<Problem>> {
|
||||
cache: &'c mut MonoCache,
|
||||
struct Env<'a, 'c, 'd, 'e, 'f, 'm, 'p, P> {
|
||||
arena: &'a Bump,
|
||||
cache: &'c mut MonoTypeCache,
|
||||
#[allow(dead_code)]
|
||||
mono_types: &'m mut MonoTypes,
|
||||
#[allow(dead_code)]
|
||||
|
@ -95,187 +100,226 @@ struct Env<'c, 'd, 'e, 'f, 'm, 'p, P: Push<Problem>> {
|
|||
debug_info: &'d mut Option<DebugInfo>,
|
||||
}
|
||||
|
||||
fn lower_var<P: Push<Problem>>(
|
||||
env: &mut Env<'_, '_, '_, '_, '_, '_, P>,
|
||||
subs: &Subs,
|
||||
var: Variable,
|
||||
) -> Option<MonoTypeId> {
|
||||
let root_var = subs.get_root_key_without_compacting(var);
|
||||
impl<'a, 'c, 'd, 'e, 'f, 'm, 'p, P: Push<Problem>> Env<'a, 'c, 'd, 'e, 'f, 'm, 'p, P> {
|
||||
fn lower_builtin(
|
||||
&mut self,
|
||||
subs: &Subs,
|
||||
symbol: Symbol,
|
||||
args: SubsSlice<Variable>,
|
||||
) -> MonoTypeId {
|
||||
if symbol == Symbol::NUM_NUM {
|
||||
number_args_to_mono_id(args, subs, self.problems)
|
||||
} else if symbol == Symbol::NUM_FLOATINGPOINT {
|
||||
num_num_args_to_mono_id(symbol, args, subs, self.problems)
|
||||
} else if symbol == Symbol::LIST_LIST {
|
||||
todo!();
|
||||
// let mut new_args = args
|
||||
// .into_iter()
|
||||
// .flat_map(|var_index| self.lower_var( subs, subs[var_index]));
|
||||
|
||||
// TODO: we could replace this cache by having Subs store a Content::Monomorphic(MonoTypeId)
|
||||
// and then overwrite it rather than having a separate cache. That memory is already in cache
|
||||
// for sure, and the lookups should be faster because they're O(1) but don't require hashing.
|
||||
// Kinda creates a cyclic dep though.
|
||||
if let Some(mono_id) = env.cache.inner.get(&root_var) {
|
||||
return Some(*mono_id);
|
||||
// let arg = new_args.next();
|
||||
} else {
|
||||
todo!("implement lower_builtin for symbol {symbol:?} - or, if all the builtins are already in here, report a compiler bug instead of panicking like this.");
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the Content to a MonoType, often by passing an iterator. None of these iterators introduce allocations.
|
||||
let mono_id = match dbg!(*subs.get_content_without_compacting(root_var)) {
|
||||
Content::Structure(flat_type) => match flat_type {
|
||||
FlatType::Apply(symbol, args) => {
|
||||
if symbol.is_builtin() {
|
||||
lower_builtin(env, subs, symbol, args)
|
||||
} else {
|
||||
todo!("handle non-builtin Apply");
|
||||
/// Exposed separately because sometimes we already looked up the Content and know it's a function,
|
||||
/// and want to continue from there without redoing the lookup.
|
||||
pub fn monomorphize_fn(
|
||||
&mut self,
|
||||
subs: &Subs,
|
||||
arg_vars: SubsSlice<Variable>,
|
||||
ret_var: Variable,
|
||||
) -> MonoTypeId {
|
||||
let func = self.lower_var(subs, ret_var);
|
||||
let mut mono_args = Vec::with_capacity_in(arg_vars.len(), self.arena);
|
||||
|
||||
mono_args.extend(
|
||||
arg_vars
|
||||
.into_iter()
|
||||
.flat_map(|var_index| self.lower_var(subs, subs[var_index])),
|
||||
);
|
||||
|
||||
let todo = (); // TODO populate debuginfo as appropriate
|
||||
|
||||
self.mono_types.add_function(func, mono_args)
|
||||
}
|
||||
|
||||
fn lower_var(&mut self, subs: &Subs, var: Variable) -> Option<MonoTypeId> {
|
||||
let root_var = subs.get_root_key_without_compacting(var);
|
||||
|
||||
// TODO: we could replace this cache by having Subs store a Content::Monomorphic(MonoTypeId)
|
||||
// and then overwrite it rather than having a separate cache. That memory is already in cache
|
||||
// for sure, and the lookups should be faster because they're O(1) but don't require hashing.
|
||||
// Kinda creates a cyclic dep though.
|
||||
if let Some(mono_id) = self.cache.inner.get(&root_var) {
|
||||
return Some(*mono_id);
|
||||
}
|
||||
|
||||
// Convert the Content to a MonoType, often by passing an iterator. None of these iterators introduce allocations.
|
||||
let mono_id = match dbg!(*subs.get_content_without_compacting(root_var)) {
|
||||
Content::Structure(flat_type) => match flat_type {
|
||||
FlatType::Apply(symbol, args) => {
|
||||
if symbol.is_builtin() {
|
||||
self.lower_builtin(subs, symbol, args)
|
||||
} else {
|
||||
todo!("handle non-builtin Apply");
|
||||
}
|
||||
}
|
||||
}
|
||||
// FlatType::Func(args, _capture, ret) => {
|
||||
// let mono_args = args
|
||||
// .into_iter()
|
||||
// .flat_map(|var_index| lower_var(env, subs[var_index]));
|
||||
FlatType::Func(args, _capture, ret) => self.monomorphize_fn(subs, args, ret),
|
||||
_ => {
|
||||
todo!();
|
||||
} /*
|
||||
FlatType::Record(fields, ext) => {
|
||||
let mut labeled_mono_ids = lower_record(env, fields, ext);
|
||||
|
||||
// let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
|
||||
// let func = lower_var(env, ret);
|
||||
// Some(env.mono_types.add_function(func, mono_args))
|
||||
// }
|
||||
_ => {
|
||||
todo!();
|
||||
} /*
|
||||
FlatType::Record(fields, ext) => {
|
||||
let mut labeled_mono_ids = lower_record(env, fields, ext);
|
||||
|
||||
// Handle the special cases of 0 fields and 1 field.
|
||||
match labeled_mono_ids.first() {
|
||||
Some((label, first_field_id)) => {
|
||||
if labeled_mono_ids.len() == 1 {
|
||||
// If we ended up with a single field, return it unwrapped.
|
||||
let todo = (); // TODO populate debuginfo using the label (if it's Some, meaning we want it)
|
||||
let todo = (); // To preserve debuginfo, we need to actually clone this mono_id and not just return the same one.
|
||||
return Some(*first_field_id);
|
||||
// Handle the special cases of 0 fields and 1 field.
|
||||
match labeled_mono_ids.first() {
|
||||
Some((label, first_field_id)) => {
|
||||
if labeled_mono_ids.len() == 1 {
|
||||
// If we ended up with a single field, return it unwrapped.
|
||||
let todo = (); // TODO populate debuginfo using the label (if it's Some, meaning we want it)
|
||||
let todo = (); // To preserve debuginfo, we need to actually clone this mono_id and not just return the same one.
|
||||
return Some(*first_field_id);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// If we ended up with an empty record,
|
||||
// after removing other empty things, return None.
|
||||
return None;
|
||||
}
|
||||
}
|
||||
None => {
|
||||
// If we ended up with an empty record,
|
||||
// after removing other empty things, return None.
|
||||
return None;
|
||||
|
||||
// Now we know we have at least 2 fields, so sort them by field name.
|
||||
// This can be unstable sort because all field names are known to be unique,
|
||||
// so sorting unstable won't be observable (and is faster than stable).
|
||||
labeled_mono_ids.sort_unstable_by(|(label1, _), (label2, _)| label1.cmp(label2));
|
||||
|
||||
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
|
||||
|
||||
// Safety: we already verified that this has at least 2 elements, and
|
||||
// we would have early returned before this point if we had fewer than 2.
|
||||
let mono_id = unsafe {
|
||||
mono_types.add_struct_unchecked(labeled_mono_ids.iter().map(|(_label, mono_id)| *mono_id))
|
||||
};
|
||||
|
||||
let labeled_indices = VecMap::from_iter(labeled_mono_ids.into_iter().enumerate().map(|(index, (label, _mono_id))| (label, MonoFieldId::new(index as u16))));
|
||||
|
||||
self.field_ids.insert(mono_id, labeled_indices);
|
||||
|
||||
Some(mono_id)
|
||||
}
|
||||
FlatType::Tuple(elems, ext) => {
|
||||
let indexed_mono_ids = lower_tuple(env, elems, ext);
|
||||
|
||||
// This can be unstable sort because all indices are known to be unique,
|
||||
// so sorting unstable won't be observable (and is faster than stable).
|
||||
indexed_mono_ids.sort_unstable_by(|(index1, _), (index2, _)| index1.cmp(index2));
|
||||
|
||||
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
|
||||
mono_types.add_struct(indexed_mono_ids.iter().map(|(_, mono_id)| *mono_id))
|
||||
}
|
||||
FlatType::TagUnion(tags, ext) => {
|
||||
let tagged_payload_ids = lower_tag_union(env, tags, ext);
|
||||
|
||||
// This can be unstable sort because all tag names are known to be unique,
|
||||
// so sorting unstable won't be observable (and is faster than stable).
|
||||
tagged_payload_ids.sort_unstable_by(|(tag1, _), (tag2, _)| tag1.cmp(tag2));
|
||||
|
||||
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
|
||||
mono_types.add_tag_union(tagged_payload_ids.iter().map(|(_, mono_id)| *mono_id))
|
||||
}
|
||||
FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => {
|
||||
// If this is still a FunctionOrTagUnion, turn it into a TagUnion.
|
||||
|
||||
// First, resolve the ext var.
|
||||
let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), *ext);
|
||||
|
||||
// Now lower all the tags we gathered from the ext var.
|
||||
// (Do this in a separate pass to avoid borrow errors on Subs.)
|
||||
lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems);
|
||||
|
||||
// Then, add the tag names with no payloads. (There are no variables to lower here.)
|
||||
for index in tag_names.into_iter() {
|
||||
tags.push(((subs[index]).clone(), Vec::new()));
|
||||
}
|
||||
|
||||
Content::Structure(FlatType::TagUnion(
|
||||
UnionTags::insert_into_subs(subs, tags),
|
||||
TagExt::Any(Variable::EMPTY_TAG_UNION),
|
||||
))
|
||||
}
|
||||
FlatType::RecursiveTagUnion(rec, tags, ext) => {
|
||||
let mut tags = resolve_tag_ext(subs, problems, *tags, *ext);
|
||||
|
||||
// Now we know we have at least 2 fields, so sort them by field name.
|
||||
// This can be unstable sort because all field names are known to be unique,
|
||||
// so sorting unstable won't be observable (and is faster than stable).
|
||||
labeled_mono_ids.sort_unstable_by(|(label1, _), (label2, _)| label1.cmp(label2));
|
||||
// Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs.
|
||||
lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems);
|
||||
|
||||
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
|
||||
|
||||
// Safety: we already verified that this has at least 2 elements, and
|
||||
// we would have early returned before this point if we had fewer than 2.
|
||||
let mono_id = unsafe {
|
||||
mono_types.add_struct_unchecked(labeled_mono_ids.iter().map(|(_label, mono_id)| *mono_id))
|
||||
};
|
||||
|
||||
let labeled_indices = VecMap::from_iter(labeled_mono_ids.into_iter().enumerate().map(|(index, (label, _mono_id))| (label, MonoFieldId::new(index as u16))));
|
||||
|
||||
env.field_ids.insert(mono_id, labeled_indices);
|
||||
|
||||
Some(mono_id)
|
||||
}
|
||||
FlatType::Tuple(elems, ext) => {
|
||||
let indexed_mono_ids = lower_tuple(env, elems, ext);
|
||||
|
||||
// This can be unstable sort because all indices are known to be unique,
|
||||
// so sorting unstable won't be observable (and is faster than stable).
|
||||
indexed_mono_ids.sort_unstable_by(|(index1, _), (index2, _)| index1.cmp(index2));
|
||||
|
||||
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
|
||||
mono_types.add_struct(indexed_mono_ids.iter().map(|(_, mono_id)| *mono_id))
|
||||
}
|
||||
FlatType::TagUnion(tags, ext) => {
|
||||
let tagged_payload_ids = lower_tag_union(env, tags, ext);
|
||||
|
||||
// This can be unstable sort because all tag names are known to be unique,
|
||||
// so sorting unstable won't be observable (and is faster than stable).
|
||||
tagged_payload_ids.sort_unstable_by(|(tag1, _), (tag2, _)| tag1.cmp(tag2));
|
||||
|
||||
let todo = (); // TODO populate debuginfo (if it's Some, meaning we want it)
|
||||
mono_types.add_tag_union(tagged_payload_ids.iter().map(|(_, mono_id)| *mono_id))
|
||||
}
|
||||
FlatType::FunctionOrTagUnion(tag_names, _symbols, ext) => {
|
||||
// If this is still a FunctionOrTagUnion, turn it into a TagUnion.
|
||||
|
||||
// First, resolve the ext var.
|
||||
let mut tags = resolve_tag_ext(subs, problems, UnionTags::default(), *ext);
|
||||
|
||||
// Now lower all the tags we gathered from the ext var.
|
||||
// (Do this in a separate pass to avoid borrow errors on Subs.)
|
||||
lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems);
|
||||
|
||||
// Then, add the tag names with no payloads. (There are no variables to lower here.)
|
||||
for index in tag_names.into_iter() {
|
||||
tags.push(((subs[index]).clone(), Vec::new()));
|
||||
Content::Structure(FlatType::RecursiveTagUnion(
|
||||
lower_var(cache, subs, problems, *rec),
|
||||
UnionTags::insert_into_subs(subs, tags),
|
||||
TagExt::Any(Variable::EMPTY_TAG_UNION),
|
||||
))
|
||||
}
|
||||
FlatType::EmptyRecord|
|
||||
FlatType::EmptyTuple |
|
||||
FlatType::EmptyTagUnion => None,
|
||||
},
|
||||
Content::Error => Content::Error,
|
||||
*/
|
||||
},
|
||||
Content::RangedNumber(range) => {
|
||||
use roc_types::num::NumericRange::*;
|
||||
|
||||
Content::Structure(FlatType::TagUnion(
|
||||
UnionTags::insert_into_subs(subs, tags),
|
||||
TagExt::Any(Variable::EMPTY_TAG_UNION),
|
||||
))
|
||||
}
|
||||
FlatType::RecursiveTagUnion(rec, tags, ext) => {
|
||||
let mut tags = resolve_tag_ext(subs, problems, *tags, *ext);
|
||||
|
||||
// Now lower all the tags we gathered. Do this in a separate pass to avoid borrow errors on Subs.
|
||||
lower_vars(tags.iter_mut().flat_map(|(_, vars)| vars.iter_mut()), cache, subs, problems);
|
||||
|
||||
Content::Structure(FlatType::RecursiveTagUnion(
|
||||
lower_var(cache, subs, problems, *rec),
|
||||
UnionTags::insert_into_subs(subs, tags),
|
||||
TagExt::Any(Variable::EMPTY_TAG_UNION),
|
||||
))
|
||||
}
|
||||
FlatType::EmptyRecord|
|
||||
FlatType::EmptyTuple |
|
||||
FlatType::EmptyTagUnion => None,
|
||||
},
|
||||
Content::Error => Content::Error,
|
||||
*/
|
||||
},
|
||||
Content::RangedNumber(range) => {
|
||||
use roc_types::num::NumericRange::*;
|
||||
|
||||
match range {
|
||||
IntAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
|
||||
IntAtLeastEitherSign(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
|
||||
NumAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
|
||||
NumAtLeastEitherSign(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
|
||||
}
|
||||
}
|
||||
Content::Alias(symbol, args, real, kind) => {
|
||||
match kind {
|
||||
AliasKind::Opaque if symbol.is_builtin() => {
|
||||
let args_slice = SubsSlice::new(args.variables_start, args.type_variables_len);
|
||||
lower_builtin(env, subs, symbol, args_slice)
|
||||
}
|
||||
_ => {
|
||||
let mono_id = lower_var(env, subs, real)?;
|
||||
let todo = (); // TODO record in debuginfo the alias name for whatever we're lowering.
|
||||
|
||||
mono_id
|
||||
match range {
|
||||
IntAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
|
||||
IntAtLeastEitherSign(int_lit_width) => {
|
||||
int_lit_width_to_mono_type_id(int_lit_width)
|
||||
}
|
||||
NumAtLeastSigned(int_lit_width) => int_lit_width_to_mono_type_id(int_lit_width),
|
||||
NumAtLeastEitherSign(int_lit_width) => {
|
||||
int_lit_width_to_mono_type_id(int_lit_width)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Content::FlexVar(_)
|
||||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _) => {
|
||||
// The only way we should reach this branch is in something like a `crash`.
|
||||
MonoTypeId::CRASH
|
||||
}
|
||||
Content::ErasedLambda | Content::LambdaSet(_) => {
|
||||
unreachable!(
|
||||
Content::Alias(symbol, args, real, kind) => {
|
||||
match kind {
|
||||
AliasKind::Opaque if symbol.is_builtin() => {
|
||||
let args_slice =
|
||||
SubsSlice::new(args.variables_start, args.type_variables_len);
|
||||
self.lower_builtin(subs, symbol, args_slice)
|
||||
}
|
||||
_ => {
|
||||
let mono_id = self.lower_var(subs, real)?;
|
||||
let todo = (); // TODO record in debuginfo the alias name for whatever we're lowering.
|
||||
|
||||
mono_id
|
||||
}
|
||||
}
|
||||
}
|
||||
Content::FlexVar(_)
|
||||
| Content::RigidVar(_)
|
||||
| Content::FlexAbleVar(_, _)
|
||||
| Content::RigidAbleVar(_, _) => {
|
||||
// The only way we should reach this branch is in something like a `crash`.
|
||||
MonoTypeId::CRASH
|
||||
}
|
||||
Content::ErasedLambda | Content::LambdaSet(_) => {
|
||||
unreachable!(
|
||||
"This new monomorphization implementation must not do anything with lambda sets, because they'll be handled later!"
|
||||
);
|
||||
}
|
||||
content => {
|
||||
todo!("specialize this Content: {content:?}");
|
||||
}
|
||||
};
|
||||
}
|
||||
content => {
|
||||
todo!("specialize this Content: {content:?}");
|
||||
}
|
||||
};
|
||||
|
||||
// This var is now known to be monomorphic, so we don't repeat this work again later.
|
||||
// (We don't insert entries for Unit values.)
|
||||
env.cache.inner.insert(root_var, mono_id);
|
||||
// This var is now known to be monomorphic, so we don't repeat this work again later.
|
||||
// (We don't insert entries for Unit values.)
|
||||
self.cache.inner.insert(root_var, mono_id);
|
||||
|
||||
Some(mono_id)
|
||||
Some(mono_id)
|
||||
}
|
||||
}
|
||||
|
||||
fn int_lit_width_to_mono_type_id(int_lit_width: roc_can::num::IntLitWidth) -> MonoTypeId {
|
||||
|
@ -521,7 +565,7 @@ fn number_args_to_mono_id(
|
|||
// labeled_mono_ids.extend(
|
||||
// fields
|
||||
// .sorted_iterator(subs, ext)
|
||||
// .map(|(label, field)| (label, lower_var(env, subs, *field.as_inner()))),
|
||||
// .map(|(label, field)| (label, self.lower_var( subs, *field.as_inner()))),
|
||||
// );
|
||||
|
||||
// // If the ext record is nonempty, set its fields to be the next ones we handle, and loop back.
|
||||
|
@ -602,29 +646,7 @@ fn number_args_to_mono_id(
|
|||
// problems: &mut impl Push<Problem>,
|
||||
// ) {
|
||||
// for var in vars {
|
||||
// if let Some(var) = lower_var(env, *var) // hmm not sure if this is still a good idea as a helper function
|
||||
// if let Some(var) = self.lower_var( *var) // hmm not sure if this is still a good idea as a helper function
|
||||
// *var = ;
|
||||
// }
|
||||
// }
|
||||
|
||||
fn lower_builtin<P: Push<Problem>>(
|
||||
env: &mut Env<'_, '_, '_, '_, '_, '_, P>,
|
||||
subs: &Subs,
|
||||
symbol: Symbol,
|
||||
args: SubsSlice<Variable>,
|
||||
) -> MonoTypeId {
|
||||
if symbol == Symbol::NUM_NUM {
|
||||
number_args_to_mono_id(args, subs, env.problems)
|
||||
} else if symbol == Symbol::NUM_FLOATINGPOINT {
|
||||
num_num_args_to_mono_id(symbol, args, subs, env.problems)
|
||||
} else if symbol == Symbol::LIST_LIST {
|
||||
todo!();
|
||||
// let mut new_args = args
|
||||
// .into_iter()
|
||||
// .flat_map(|var_index| lower_var(env, subs, subs[var_index]));
|
||||
|
||||
// let arg = new_args.next();
|
||||
} else {
|
||||
todo!("implement lower_builtin for symbol {symbol:?} - or, if all the builtins are already in here, report a compiler bug instead of panicking like this.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use roc_load::LoadedModule;
|
|||
use roc_region::all::Region;
|
||||
use roc_solve::FunctionKind;
|
||||
use roc_specialize_types::{
|
||||
DebugInfo, Env, Interns, MonoCache, MonoExpr, MonoExprs, MonoTypes, RecordFieldIds,
|
||||
DebugInfo, Env, Interns, MonoExpr, MonoExprs, MonoTypeCache, MonoTypes, RecordFieldIds,
|
||||
TupleElemIds,
|
||||
};
|
||||
use test_compile::{trim_and_deindent, SpecializedExprOut};
|
||||
|
@ -59,7 +59,7 @@ fn specialize_expr<'a>(
|
|||
|
||||
let mut problems = Vec::new();
|
||||
let mut debug_info: Option<DebugInfo> = None;
|
||||
let mut types_cache = MonoCache::from_solved_subs(&solved);
|
||||
let mut types_cache = MonoTypeCache::from_solved_subs(&solved);
|
||||
let mut mono_types = MonoTypes::new();
|
||||
let mut mono_exprs = MonoExprs::new();
|
||||
|
||||
|
@ -152,10 +152,10 @@ fn dbg_mono_expr_help<'a>(
|
|||
MonoExpr::Number(number) => {
|
||||
write!(buf, "Number({:?})", number).unwrap();
|
||||
}
|
||||
MonoExpr::Struct(expr_ids) => {
|
||||
MonoExpr::Struct(field_exprs) => {
|
||||
write!(buf, "Struct([").unwrap();
|
||||
|
||||
for (index, expr) in mono_exprs.iter_slice(expr_ids.as_slice()).enumerate() {
|
||||
for (index, expr) in mono_exprs.iter_slice(field_exprs.as_slice()).enumerate() {
|
||||
if index > 0 {
|
||||
write!(buf, ", ").unwrap();
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue