Unwrap layouts containing void layouts as newtypes

Addresses the attempt to do so in https://github.com/roc-lang/roc/pull/3465

Co-authored-by: Folkert <folkert@folkertdev.nl>
This commit is contained in:
Ayaz Hafiz 2022-09-19 16:47:27 -05:00
parent 639c1e5fa3
commit f41936d5e5
No known key found for this signature in database
GPG Key ID: 0E2A37416A25EF58
9 changed files with 228 additions and 14 deletions

1
Cargo.lock generated
View File

@ -3892,6 +3892,7 @@ dependencies = [
name = "roc_mono"
version = "0.0.1"
dependencies = [
"bitvec 1.0.1",
"bumpalo",
"hashbrown 0.12.3",
"roc_builtins",

View File

@ -27,3 +27,4 @@ ven_pretty = { path = "../../vendor/pretty" }
bumpalo = { version = "3.11.0", features = ["collections"] }
hashbrown = { version = "0.12.3", features = [ "bumpalo" ] }
static_assertions = "1.1.0"
bitvec = "1.0.1"

View File

@ -4,6 +4,7 @@ use crate::ir::{
use crate::layout::{Builtin, Layout, LayoutCache, TagIdIntType, UnionLayout};
use roc_builtins::bitcode::{FloatWidth, IntWidth};
use roc_collections::all::{MutMap, MutSet};
use roc_error_macros::internal_error;
use roc_exhaustive::{Ctor, CtorName, RenderAs, TagId, Union};
use roc_module::ident::TagName;
use roc_module::low_level::LowLevel;
@ -577,6 +578,8 @@ fn test_at_path<'a>(
arguments: arguments.to_vec(),
},
Voided { .. } => internal_error!("unreachable"),
OpaqueUnwrap { opaque, argument } => {
let union = Union {
render_as: RenderAs::Tag,
@ -875,6 +878,7 @@ fn to_relevant_branch_help<'a>(
_ => None,
}
}
Voided { .. } => internal_error!("unreachable"),
StrLiteral(string) => match test {
IsStr(test_str) if string == *test_str => {
start.extend(end);
@ -1018,6 +1022,8 @@ fn needs_tests(pattern: &Pattern) -> bool {
| FloatLiteral(_, _)
| DecimalLiteral(_)
| StrLiteral(_) => true,
Voided { .. } => internal_error!("unreachable"),
}
}

View File

@ -5781,6 +5781,41 @@ fn convert_tag_union<'a>(
let iter = field_symbols_temp.into_iter().map(|(_, _, data)| data);
assign_to_symbols(env, procs, layout_cache, iter, stmt)
}
NewtypeByVoid {
data_tag_arguments: field_layouts,
data_tag_name,
..
} => {
let dataful_tag = data_tag_name.expect_tag();
if dataful_tag != tag_name {
// this tag is not represented, and hence will never be reached, at runtime.
Stmt::RuntimeError("voided tag constructor is unreachable")
} else {
let field_symbols_temp = sorted_field_symbols(env, procs, layout_cache, args);
let mut field_symbols = Vec::with_capacity_in(field_layouts.len(), env.arena);
field_symbols.extend(field_symbols_temp.iter().map(|r| r.1));
let field_symbols = field_symbols.into_bump_slice();
// Layout will unpack this unwrapped tack if it only has one (non-zero-sized) field
let layout = layout_cache
.from_var(env.arena, variant_var, env.subs)
.unwrap_or_else(|err| panic!("TODO turn fn_var into a RuntimeError {:?}", err));
// even though this was originally a Tag, we treat it as a Struct from now on
let 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)
};
let iter = field_symbols_temp.into_iter().map(|(_, _, data)| data);
assign_to_symbols(env, procs, layout_cache, iter, stmt)
}
}
Wrapped(variant) => {
let (tag_id, _) = variant.tag_name_to_id(&tag_name);
@ -6520,7 +6555,11 @@ fn from_can_when<'a>(
let arena = env.arena;
let it = opt_branches
.into_iter()
.map(|(pattern, opt_guard, can_expr)| {
.filter_map(|(pattern, opt_guard, can_expr)| {
if pattern.is_voided() {
return None;
}
let branch_stmt = match join_point {
None => from_can(env, expr_var, can_expr, procs, layout_cache),
Some(id) => {
@ -6548,7 +6587,7 @@ fn from_can_when<'a>(
jump,
);
(
Some((
pattern.clone(),
Guard::Guard {
id,
@ -6556,9 +6595,9 @@ fn from_can_when<'a>(
stmt: guard_stmt,
},
branch_stmt,
)
))
} else {
(pattern, Guard::NoGuard, branch_stmt)
Some((pattern, Guard::NoGuard, branch_stmt))
}
});
let mono_branches = Vec::from_iter_in(it, arena);
@ -7085,6 +7124,10 @@ fn store_pattern_help<'a>(
stmt,
);
}
Voided { .. } => {
return StorePattern::NotProductive(stmt);
}
OpaqueUnwrap { argument, .. } => {
let (pattern, _layout) = &**argument;
return store_pattern_help(env, procs, layout_cache, pattern, outer_symbol, stmt);
@ -8676,12 +8719,56 @@ pub enum Pattern<'a> {
layout: UnionLayout<'a>,
union: roc_exhaustive::Union,
},
Voided {
tag_name: TagName,
},
OpaqueUnwrap {
opaque: Symbol,
argument: Box<(Pattern<'a>, Layout<'a>)>,
},
}
impl<'a> Pattern<'a> {
/// This pattern contains a pattern match on Void (i.e. [], the empty tag union)
/// such branches are not reachable at runtime
pub fn is_voided(&self) -> bool {
let mut stack: std::vec::Vec<&Pattern> = vec![self];
while let Some(pattern) = stack.pop() {
match pattern {
Pattern::Identifier(_)
| Pattern::Underscore
| Pattern::IntLiteral(_, _)
| Pattern::FloatLiteral(_, _)
| Pattern::DecimalLiteral(_)
| Pattern::BitLiteral { .. }
| Pattern::EnumLiteral { .. }
| Pattern::StrLiteral(_) => { /* terminal */ }
Pattern::RecordDestructure(destructs, _) => {
for destruct in destructs {
match &destruct.typ {
DestructType::Required(_) => { /* do nothing */ }
DestructType::Guard(pattern) => {
stack.push(pattern);
}
}
}
}
Pattern::NewtypeDestructure { arguments, .. } => {
stack.extend(arguments.iter().map(|(t, _)| t))
}
Pattern::Voided { .. } => return true,
Pattern::AppliedTag { arguments, .. } => {
stack.extend(arguments.iter().map(|(t, _)| t))
}
Pattern::OpaqueUnwrap { argument, .. } => stack.push(&argument.0),
}
}
false
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RecordDestruct<'a> {
pub label: Lowercase,
@ -8907,6 +8994,57 @@ fn from_can_pattern_help<'a>(
arguments: mono_args,
}
}
NewtypeByVoid {
data_tag_arguments,
data_tag_name,
..
} => {
let data_tag_name = data_tag_name.expect_tag();
if tag_name != &data_tag_name {
// this tag is not represented at runtime
Pattern::Voided {
tag_name: tag_name.clone(),
}
} else {
let mut arguments = arguments.clone();
arguments.sort_by(|arg1, arg2| {
let size1 = layout_cache
.from_var(env.arena, arg1.0, env.subs)
.map(|x| x.alignment_bytes(&layout_cache.interner, env.target_info))
.unwrap_or(0);
let size2 = layout_cache
.from_var(env.arena, arg2.0, env.subs)
.map(|x| x.alignment_bytes(&layout_cache.interner, env.target_info))
.unwrap_or(0);
size2.cmp(&size1)
});
let mut mono_args = Vec::with_capacity_in(arguments.len(), env.arena);
let it = arguments.iter().zip(data_tag_arguments.iter());
for ((_, loc_pat), layout) in it {
mono_args.push((
from_can_pattern_help(
env,
procs,
layout_cache,
&loc_pat.value,
assignments,
)?,
*layout,
));
}
Pattern::NewtypeDestructure {
tag_name: tag_name.clone(),
arguments: mono_args,
}
}
}
Wrapped(variant) => {
let (tag_id, argument_layouts) = variant.tag_name_to_id(tag_name);
let number_of_tags = variant.number_of_tags();

View File

@ -1,4 +1,5 @@
use crate::ir::Parens;
use bitvec::vec::BitVec;
use bumpalo::collections::Vec;
use bumpalo::Bump;
use roc_builtins::bitcode::{FloatWidth, IntWidth};
@ -3279,6 +3280,11 @@ pub enum UnionVariant<'a> {
tag_name: TagOrClosure,
arguments: Vec<'a, Layout<'a>>,
},
NewtypeByVoid {
data_tag_name: TagOrClosure,
data_tag_id: TagIdIntType,
data_tag_arguments: Vec<'a, Layout<'a>>,
},
Wrapped(WrappedVariant<'a>),
}
@ -3525,6 +3531,8 @@ where
Vec::with_capacity_in(tags_list.len(), env.arena);
let mut has_any_arguments = false;
let mut inhabited_tag_ids = BitVec::<usize>::repeat(true, num_tags);
for &(tag_name, arguments) in tags_list.into_iter() {
let mut arg_layouts = Vec::with_capacity_in(arguments.len() + 1, env.arena);
@ -3536,6 +3544,10 @@ where
has_any_arguments = true;
arg_layouts.push(layout);
if layout == Layout::VOID {
inhabited_tag_ids.set(answer.len(), false);
}
}
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
// If we encounter an unbound type var (e.g. `Ok *`)
@ -3560,6 +3572,18 @@ where
answer.push((tag_name.clone().into(), arg_layouts.into_bump_slice()));
}
if inhabited_tag_ids.count_ones() == 1 {
let kept_tag_id = inhabited_tag_ids.first_one().unwrap();
let kept = answer.get(kept_tag_id).unwrap();
let variant = UnionVariant::NewtypeByVoid {
data_tag_name: kept.0.clone(),
data_tag_id: kept_tag_id as _,
data_tag_arguments: Vec::from_iter_in(kept.1.iter().copied(), env.arena),
};
return Cacheable(variant, cache_criteria);
}
match num_tags {
2 if !has_any_arguments => {
// type can be stored in a boolean
@ -3680,6 +3704,7 @@ where
let mut has_any_arguments = false;
let mut nullable = None;
let mut inhabited_tag_ids = BitVec::<usize>::repeat(true, num_tags);
// only recursive tag unions can be nullable
let is_recursive = opt_rec_var.is_some();
@ -3721,6 +3746,10 @@ where
} else {
arg_layouts.push(layout);
}
if layout == Layout::VOID {
inhabited_tag_ids.set(answer.len(), false);
}
}
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
// If we encounter an unbound type var (e.g. `Ok *`)
@ -3746,6 +3775,18 @@ where
answer.push((tag_name.into(), arg_layouts.into_bump_slice()));
}
if inhabited_tag_ids.count_ones() == 1 && !is_recursive {
let kept_tag_id = inhabited_tag_ids.first_one().unwrap();
let kept = answer.get(kept_tag_id).unwrap();
let variant = UnionVariant::NewtypeByVoid {
data_tag_name: kept.0.clone(),
data_tag_id: kept_tag_id as _,
data_tag_arguments: Vec::from_iter_in(kept.1.iter().copied(), env.arena),
};
return Cacheable(variant, cache_criteria);
}
match num_tags {
2 if !has_any_arguments => {
// type can be stored in a boolean
@ -3870,6 +3911,15 @@ where
answer1
}
NewtypeByVoid {
data_tag_arguments, ..
} => {
if data_tag_arguments.len() == 1 {
data_tag_arguments[0]
} else {
Layout::struct_no_name_order(data_tag_arguments.into_bump_slice())
}
}
Wrapped(variant) => {
use WrappedVariant::*;

View File

@ -262,8 +262,9 @@ fn list_map_try_ok() {
r#"
List.mapTry [1, 2, 3] \elem -> Ok elem
"#,
RocResult::ok(RocList::<i64>::from_slice(&[1, 2, 3])),
RocResult<RocList<i64>, ()>
// Result I64 [] is unwrapped to just I64
RocList::<i64>::from_slice(&[1, 2, 3]),
RocList<i64>
);
assert_evals_to!(
// Transformation
@ -273,12 +274,13 @@ fn list_map_try_ok() {
Ok "\(str)!"
"#,
RocResult::ok(RocList::<RocStr>::from_slice(&[
// Result Str [] is unwrapped to just Str
RocList::<RocStr>::from_slice(&[
RocStr::from("2!"),
RocStr::from("4!"),
RocStr::from("6!"),
])),
RocResult<RocList<RocStr>, ()>
]),
RocList<RocStr>
);
}

View File

@ -1,7 +1,3 @@
procedure Test.0 ():
let Test.5 : Str = "abc";
let Test.1 : [C [], C Str] = TagId(1) Test.5;
let Test.3 : Str = UnionAtIndex (Id 1) (Index 0) Test.1;
inc Test.3;
dec Test.1;
ret Test.3;
ret Test.5;

View File

@ -0,0 +1,7 @@
procedure Test.0 ():
let Test.7 : Int1 = true;
if Test.7 then
Error voided tag constructor is unreachable
else
let Test.6 : Str = "abc";
ret Test.6;

View File

@ -1949,3 +1949,16 @@ fn match_on_result_with_uninhabited_error_branch() {
"#
)
}
#[mono_test]
fn unreachable_void_constructor() {
indoc!(
r#"
app "test" provides [main] to "./platform"
x : []
main = if True then Ok x else Err "abc"
"#
)
}