mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-20 07:17:50 +03:00
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:
parent
639c1e5fa3
commit
f41936d5e5
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3892,6 +3892,7 @@ dependencies = [
|
||||
name = "roc_mono"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bitvec 1.0.1",
|
||||
"bumpalo",
|
||||
"hashbrown 0.12.3",
|
||||
"roc_builtins",
|
||||
|
@ -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"
|
||||
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
|
@ -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::*;
|
||||
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
@ -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"
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user