Do not discard empty types in specialize_types

Currently, `to_mono_expr` returns `Nothing` when it encounters
an empty record and it discards fields that are empty.

For simplicity, we decided to do this at a later stage, so this changes
it to return a new `MonoExpr::Unit` type instead.
This commit is contained in:
Agus Zubiaga 2024-12-11 10:05:55 -03:00
parent a5bcf55d08
commit 4b28136143
No known key found for this signature in database
5 changed files with 61 additions and 64 deletions

View file

@ -84,7 +84,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
}
}
pub fn to_mono_expr(&mut self, can_expr: &Expr) -> Option<MonoExpr> {
pub fn to_mono_expr(&mut self, can_expr: &Expr) -> MonoExpr {
let problems = &mut self.problems;
let mono_types = &mut self.mono_types;
let mut mono_from_var = |var| {
@ -103,7 +103,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
macro_rules! compiler_bug {
($problem:expr) => {{
problems.push($problem);
Some(MonoExpr::CompilerBug($problem))
MonoExpr::CompilerBug($problem)
}};
}
@ -112,13 +112,11 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
match self.subs.get_content_without_compacting(*var) {
Content::FlexVar(_) => {
// Plain decimal number literals like `4.2` can still have an unbound var.
Some(MonoExpr::Number(Number::Dec(*val)))
MonoExpr::Number(Number::Dec(*val))
}
_ => match mono_from_var(*var) {
Some(mono_id) => match mono_types.get(mono_id) {
MonoType::Primitive(primitive) => {
Some(to_frac(*primitive, *val, problems))
}
MonoType::Primitive(primitive) => to_frac(*primitive, *val, problems),
other => {
compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other)))
}
@ -133,9 +131,7 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
// Number literals and int literals both specify integer numbers, so to_num() can work on both.
match mono_from_var(*var) {
Some(mono_id) => match mono_types.get(mono_id) {
MonoType::Primitive(primitive) => {
Some(to_num(*primitive, *int_value, problems))
}
MonoType::Primitive(primitive) => to_num(*primitive, *int_value, problems),
other => compiler_bug!(Problem::NumSpecializedToWrongType(Some(*other))),
},
None => compiler_bug!(Problem::NumSpecializedToWrongType(None)),
@ -147,21 +143,19 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
// or keeping a separate value but storing an IntValue instead of a char), then
// even though we verify them differently, we can combine this branch with Num and Int.
Some(mono_id) => match mono_types.get(mono_id) {
MonoType::Primitive(primitive) => {
Some(char_to_int(*primitive, *char, problems))
}
MonoType::Primitive(primitive) => char_to_int(*primitive, *char, problems),
other => compiler_bug!(Problem::CharSpecializedToWrongType(Some(*other))),
},
None => compiler_bug!(Problem::CharSpecializedToWrongType(None)),
},
Expr::Str(contents) => Some(MonoExpr::Str(self.string_interns.get_id(
Expr::Str(contents) => MonoExpr::Str(self.string_interns.get_id(
self.arena,
// TODO should be able to remove this alloc_str() once canonical Expr stores an arena-allocated string.
self.arena.alloc_str(contents),
))),
)),
Expr::EmptyRecord => {
// Empty records are zero-sized and should be discarded.
None
MonoExpr::Unit
}
Expr::Record {
record_var: _,
@ -172,10 +166,10 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
// Check for records with 0-1 fields before sorting or reserving a slice of IDs (which might be unnecessary).
// We'll check again after discarding zero-sized fields, because we might end up with 0 or 1 fields remaining.
if fields.len() <= 1 {
return fields
.into_iter()
.next()
.and_then(|(_, field)| self.to_mono_expr(&field.loc_expr.value));
return match fields.into_iter().next() {
Some((_, field)) => self.to_mono_expr(&field.loc_expr.value),
None => MonoExpr::Unit,
};
}
// Sort the fields alphabetically by name.
@ -189,21 +183,18 @@ impl<'a, 'c, 'd, 'i, 's, 't, P: Push<Problem>> Env<'a, 'c, 'd, 'i, 's, 't, P> {
let mut buf: Vec<(MonoExpr, Region)> =
Vec::with_capacity_in(fields.len(), self.arena);
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))
}),
);
buf.extend(fields.into_iter().map(|(_name, field)| {
(
self.to_mono_expr(&field.loc_expr.value),
field.loc_expr.region,
)
}));
// If we ended up with exactly 1 field, return it unwrapped.
if buf.len() == 1 {
return buf.pop().map(|(expr, _region)| expr);
}
let slice = unsafe {
NonEmptySlice::from_slice_unchecked(self.mono_exprs.extend(buf.into_iter()))
};
NonEmptySlice::from_slice(self.mono_exprs.extend(buf.iter().copied()))
.map(MonoExpr::Struct)
MonoExpr::Struct(slice)
}
// Expr::Call((fn_var, fn_expr, capture_var, ret_var), args, called_via) => {
// let opt_ret_type = mono_from_var(*var);

View file

@ -129,14 +129,18 @@ impl MonoExprs {
})
}
pub fn extend(
&mut self,
exprs: impl Iterator<Item = (MonoExpr, Region)> + Clone,
) -> Slice<MonoExpr> {
pub fn extend(&mut self, exprs: impl Iterator<Item = (MonoExpr, Region)>) -> 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 (size_hint, _) = exprs.size_hint();
self.exprs.reserve(size_hint);
self.regions.reserve(size_hint);
for (expr, region) in exprs {
self.exprs.push(expr);
self.regions.push(region);
}
let len = self.exprs.len() - start;
@ -279,6 +283,8 @@ pub enum MonoExpr {
recursive: Recursive,
},
Unit,
/// A record literal or a tuple literal.
/// These have already been sorted alphabetically.
Struct(NonEmptySlice<MonoExpr>),

View file

@ -86,9 +86,8 @@ fn specialize_expr<'a>(
assert_eq!(0, home_decls.expressions.len());
let region = Region::zero();
let mono_expr_id = env
.to_mono_expr(&main_expr)
.map(|mono_expr| mono_exprs.add(mono_expr, region));
let mono_expr = env.to_mono_expr(&main_expr);
let mono_expr_id = mono_exprs.add(mono_expr, region);
SpecializedExprOut {
mono_expr_id,
@ -100,13 +99,13 @@ fn specialize_expr<'a>(
}
#[track_caller]
pub fn expect_no_expr(input: impl AsRef<str>) {
pub fn expect_unit(input: impl AsRef<str>) {
let arena = Bump::new();
let mut interns = Interns::new();
let out = specialize_expr(&arena, input.as_ref(), &mut interns);
let actual = out.mono_expr_id.map(|id| out.mono_exprs.get_expr(id));
let actual = out.mono_exprs.get_expr(out.mono_expr_id);
assert_eq!(None, actual, "This input expr should have specialized to being dicarded as zero-sized, but it didn't: {:?}", input.as_ref());
assert_eq!(MonoExpr::Unit, *actual, "This input expr should have specialized to being dicarded as zero-sized, but it didn't: {:?}", input.as_ref());
}
#[track_caller]
@ -165,6 +164,9 @@ fn dbg_mono_expr_help<'a>(
write!(buf, "])").unwrap();
}
MonoExpr::Unit => {
write!(buf, "{{}}").unwrap();
}
// MonoExpr::List { elem_type, elems } => todo!(),
// MonoExpr::Lookup(symbol, mono_type_id) => todo!(),
// MonoExpr::ParameterizedLookup {
@ -270,11 +272,8 @@ pub fn expect_mono_expr_custom<T: PartialEq + core::fmt::Debug>(
let arena = Bump::new();
let mut string_interns = Interns::new();
let out = specialize_expr(&arena, input.as_ref(), &mut string_interns);
let mono_expr_id = out
.mono_expr_id
.expect("This input expr should not have been discarded as zero-sized, but it was discarded: {input:?}");
let actual_expr = out.mono_exprs.get_expr(mono_expr_id); // Must run first, to populate string interns!
let actual_expr = out.mono_exprs.get_expr(out.mono_expr_id); // Must run first, to populate string interns!
let actual = to_actual(&arena, &out.mono_exprs, &string_interns, actual_expr);
let expected = to_expected(&arena, &out.mono_exprs, &string_interns);

View file

@ -10,16 +10,16 @@ mod specialize_structs {
use crate::helpers::expect_mono_expr_str;
use super::helpers::{expect_mono_expr_with_interns, expect_no_expr};
use super::helpers::{expect_mono_expr_with_interns, expect_unit};
#[test]
fn empty_record() {
expect_no_expr("{}");
expect_unit("{}");
}
#[test]
fn one_field_with_empty_record() {
expect_no_expr("{ discardedField: {} }");
expect_unit("{ discardedField: {} }");
}
#[test]
@ -31,16 +31,6 @@ mod specialize_structs {
});
}
#[test]
fn one_field_after_dropping_zero_sized() {
let string = "foo";
let expected =
format!("{{ discarded: {{}}, discardedToo: \"{string}\", alsoDiscarded: {{}} }}");
expect_mono_expr_with_interns(expected, |arena, interns| {
MonoExpr::Str(interns.try_get_id(arena, string).unwrap())
});
}
#[test]
fn two_fields() {
let one = 42;
@ -51,4 +41,15 @@ mod specialize_structs {
format!("Struct([Number(I8({one})), Number(I8({two}))])"),
);
}
#[test]
fn two_fields_one_unit() {
let one = 42;
let two = "{}";
expect_mono_expr_str(
format!("{{ one: {one}, two: {two} }}"),
format!("Struct([Number(I8({one})), {{}}])"),
);
}
}

View file

@ -8,7 +8,7 @@ use roc_specialize_types::{
#[derive(Debug)]
pub struct SpecializedExprOut {
pub mono_expr_id: Option<MonoExprId>,
pub mono_expr_id: MonoExprId,
pub region: Region,
pub mono_types: MonoTypes,
pub mono_exprs: MonoExprs,
@ -47,8 +47,8 @@ impl SpecializedExpr {
&mut problems,
);
env.to_mono_expr(&solved_out.expr)
.map(|mono_expr| mono_exprs.add(mono_expr, Region::zero()))
let mono_expr = env.to_mono_expr(&solved_out.expr);
mono_exprs.add(mono_expr, Region::zero())
};
SpecializedExprOut {