mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 04:08:19 +00:00
Merge pull request #4076 from roc-lang/collapse-void-2
Unwrap layouts containing void layouts as newtypes
This commit is contained in:
commit
a74f5d9366
12 changed files with 330 additions and 38 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -3895,6 +3895,7 @@ dependencies = [
|
|||
name = "roc_mono"
|
||||
version = "0.0.1"
|
||||
dependencies = [
|
||||
"bitvec 1.0.1",
|
||||
"bumpalo",
|
||||
"hashbrown 0.12.3",
|
||||
"roc_builtins",
|
||||
|
|
|
@ -393,11 +393,11 @@ contains = \list, needle ->
|
|||
## `fold`, `foldLeft`, or `foldl`.
|
||||
walk : List elem, state, (state, elem -> state) -> state
|
||||
walk = \list, state, func ->
|
||||
walkHelp : _, _ -> [Continue _, Break []]
|
||||
walkHelp = \currentState, element -> Continue (func currentState element)
|
||||
|
||||
when List.iterate list state walkHelp is
|
||||
Continue newState -> newState
|
||||
Break void -> List.unreachable void
|
||||
|
||||
## Note that in other languages, `walkBackwards` is sometimes called `reduceRight`,
|
||||
## `fold`, `foldRight`, or `foldr`.
|
||||
|
@ -1006,6 +1006,3 @@ iterBackwardsHelp = \list, state, f, prevIndex ->
|
|||
Break b -> Break b
|
||||
else
|
||||
Continue state
|
||||
|
||||
## useful for typechecking guaranteed-unreachable cases
|
||||
unreachable : [] -> a
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5731,7 +5731,7 @@ fn convert_tag_union<'a>(
|
|||
"The `[]` type has no constructors, source var {:?}",
|
||||
variant_var
|
||||
),
|
||||
Unit | UnitWithArguments => Stmt::Let(assigned, Expr::Struct(&[]), Layout::UNIT, hole),
|
||||
Unit => Stmt::Let(assigned, Expr::Struct(&[]), Layout::UNIT, hole),
|
||||
BoolUnion { ttrue, .. } => Stmt::Let(
|
||||
assigned,
|
||||
Expr::Literal(Literal::Bool(&tag_name == ttrue.expect_tag_ref())),
|
||||
|
@ -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,23 @@ 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 the pattern has a void layout we can drop it; however, we must still perform the
|
||||
// work of building the body, because that may contain specializations we must
|
||||
// discover for use elsewhere. See
|
||||
// `unreachable_branch_is_eliminated_but_produces_lambda_specializations` in test_mono
|
||||
// for an example.
|
||||
let should_eliminate_branch = pattern.is_voided();
|
||||
|
||||
// If we're going to eliminate the branch, we need to take a snapshot of the symbol
|
||||
// specializations before we enter the branch, because any new specializations that
|
||||
// will be added in the branch body will never need to be resolved!
|
||||
let specialization_symbol_snapshot = if should_eliminate_branch {
|
||||
Some(std::mem::take(&mut procs.symbol_specializations))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let branch_stmt = match join_point {
|
||||
None => from_can(env, expr_var, can_expr, procs, layout_cache),
|
||||
Some(id) => {
|
||||
|
@ -6533,7 +6584,7 @@ fn from_can_when<'a>(
|
|||
};
|
||||
|
||||
use crate::decision_tree::Guard;
|
||||
if let Some(loc_expr) = opt_guard {
|
||||
let result = if let Some(loc_expr) = opt_guard {
|
||||
let id = JoinPointId(env.unique_symbol());
|
||||
let symbol = env.unique_symbol();
|
||||
let jump = env.arena.alloc(Stmt::Jump(id, env.arena.alloc([symbol])));
|
||||
|
@ -6559,6 +6610,13 @@ fn from_can_when<'a>(
|
|||
)
|
||||
} else {
|
||||
(pattern, Guard::NoGuard, branch_stmt)
|
||||
};
|
||||
|
||||
if should_eliminate_branch {
|
||||
procs.symbol_specializations = specialization_symbol_snapshot.unwrap();
|
||||
None
|
||||
} else {
|
||||
Some(result)
|
||||
}
|
||||
});
|
||||
let mono_branches = Vec::from_iter_in(it, arena);
|
||||
|
@ -7085,6 +7143,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 +8738,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,
|
||||
|
@ -8808,7 +8914,7 @@ fn from_can_pattern_help<'a>(
|
|||
"there is no pattern of type `[]`, union var {:?}",
|
||||
*whole_var
|
||||
),
|
||||
Unit | UnitWithArguments => Pattern::EnumLiteral {
|
||||
Unit => Pattern::EnumLiteral {
|
||||
tag_id: 0,
|
||||
tag_name: tag_name.clone(),
|
||||
union: Union {
|
||||
|
@ -8907,6 +9013,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};
|
||||
|
@ -3270,7 +3271,6 @@ impl From<Symbol> for TagOrClosure {
|
|||
pub enum UnionVariant<'a> {
|
||||
Never,
|
||||
Unit,
|
||||
UnitWithArguments,
|
||||
BoolUnion {
|
||||
ttrue: TagOrClosure,
|
||||
ffalse: TagOrClosure,
|
||||
|
@ -3280,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>),
|
||||
}
|
||||
|
||||
|
@ -3526,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);
|
||||
|
||||
|
@ -3537,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 *`)
|
||||
|
@ -3561,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
|
||||
|
@ -3628,19 +3651,13 @@ where
|
|||
|
||||
// just one tag in the union (but with arguments) can be a struct
|
||||
let mut layouts = Vec::with_capacity_in(tags_vec.len(), env.arena);
|
||||
let mut contains_zero_sized = false;
|
||||
|
||||
for var in arguments {
|
||||
let Cacheable(result, criteria) = Layout::from_var(env, var);
|
||||
cache_criteria.and(criteria);
|
||||
match result {
|
||||
Ok(layout) => {
|
||||
// Drop any zero-sized arguments like {}
|
||||
if !layout.is_dropped_because_empty() {
|
||||
layouts.push(layout);
|
||||
} else {
|
||||
contains_zero_sized = true;
|
||||
}
|
||||
layouts.push(layout);
|
||||
}
|
||||
Err(LayoutProblem::UnresolvedTypeVar(_)) => {
|
||||
// If we encounter an unbound type var (e.g. `Ok *`)
|
||||
|
@ -3663,11 +3680,7 @@ where
|
|||
});
|
||||
|
||||
if layouts.is_empty() {
|
||||
if contains_zero_sized {
|
||||
Cacheable(UnionVariant::UnitWithArguments, cache_criteria)
|
||||
} else {
|
||||
Cacheable(UnionVariant::Unit, cache_criteria)
|
||||
}
|
||||
Cacheable(UnionVariant::Unit, cache_criteria)
|
||||
} else if let Some(rec_var) = opt_rec_var {
|
||||
let variant = UnionVariant::Wrapped(WrappedVariant::NonNullableUnwrapped {
|
||||
tag_name: tag_name.into(),
|
||||
|
@ -3691,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();
|
||||
|
@ -3732,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 *`)
|
||||
|
@ -3757,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
|
||||
|
@ -3866,7 +3896,7 @@ where
|
|||
|
||||
let result = match variant {
|
||||
Never => Layout::VOID,
|
||||
Unit | UnitWithArguments => Layout::UNIT,
|
||||
Unit => Layout::UNIT,
|
||||
BoolUnion { .. } => Layout::bool(),
|
||||
ByteUnion(_) => Layout::u8(),
|
||||
Newtype {
|
||||
|
@ -3881,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>
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -3004,8 +3006,10 @@ fn list_find_empty_layout() {
|
|||
List.findFirst [] \_ -> True
|
||||
"#
|
||||
),
|
||||
RocResult::err(()),
|
||||
RocResult<(), ()>
|
||||
// [Ok [], Err [NotFound]] gets unwrapped all the way to just [NotFound],
|
||||
// which is the unit!
|
||||
(),
|
||||
()
|
||||
);
|
||||
|
||||
assert_evals_to!(
|
||||
|
@ -3014,8 +3018,10 @@ fn list_find_empty_layout() {
|
|||
List.findLast [] \_ -> True
|
||||
"#
|
||||
),
|
||||
RocResult::err(()),
|
||||
RocResult<(), ()>
|
||||
// [Ok [], Err [NotFound]] gets unwrapped all the way to just [NotFound],
|
||||
// which is the unit!
|
||||
(),
|
||||
()
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,29 @@
|
|||
procedure Test.1 (Test.2):
|
||||
let Test.3 : Int1 = false;
|
||||
ret Test.3;
|
||||
|
||||
procedure Test.3 (Test.13):
|
||||
let Test.15 : Str = "t1";
|
||||
ret Test.15;
|
||||
|
||||
procedure Test.4 (Test.16):
|
||||
let Test.18 : Str = "t2";
|
||||
ret Test.18;
|
||||
|
||||
procedure Test.0 ():
|
||||
let Test.19 : Str = "abc";
|
||||
let Test.6 : Int1 = CallByName Test.1 Test.19;
|
||||
dec Test.19;
|
||||
let Test.9 : {} = Struct {};
|
||||
joinpoint Test.10 Test.8:
|
||||
ret Test.8;
|
||||
in
|
||||
switch Test.6:
|
||||
case 0:
|
||||
let Test.11 : Str = CallByName Test.3 Test.9;
|
||||
jump Test.10 Test.11;
|
||||
|
||||
default:
|
||||
let Test.12 : Str = CallByName Test.4 Test.9;
|
||||
jump Test.10 Test.12;
|
||||
|
|
@ -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,54 @@ 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"
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
||||
#[mono_test]
|
||||
fn unreachable_branch_is_eliminated_but_produces_lambda_specializations() {
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
provideThunk = \x ->
|
||||
when x is
|
||||
Ok _ ->
|
||||
t1 = \{} -> "t1"
|
||||
t1
|
||||
# During specialization of `main` we specialize this function,
|
||||
# which leads to elimination of this branch, because it is unreachable
|
||||
# (it can only match the uninhabited type `Err []`).
|
||||
#
|
||||
# However, naive elimination of this branch would mean we don't traverse
|
||||
# the branch body. If we don't do so, we will fail to see and specialize `t2`,
|
||||
# which is problematic - while `t2` won't ever be reached in this specialization,
|
||||
# it is still part of the lambda set, and `thunk {}` (in main) will match over
|
||||
# it before calling.
|
||||
#
|
||||
# So, this test verifies that we eliminate this branch, but still specialize
|
||||
# everything we need.
|
||||
Err _ ->
|
||||
t2 = \{} -> "t2"
|
||||
t2
|
||||
|
||||
main =
|
||||
x : Result Str []
|
||||
x = Ok "abc"
|
||||
|
||||
thunk = provideThunk x
|
||||
|
||||
thunk {}
|
||||
"#
|
||||
)
|
||||
}
|
||||
|
|
|
@ -88,7 +88,8 @@ export fn roc_memset(dst: [*]u8, value: i32, size: usize) callconv(.C) void {
|
|||
const Unit = extern struct {};
|
||||
|
||||
pub export fn main() callconv(.C) u8 {
|
||||
const size = @intCast(usize, roc__mainForHost_size());
|
||||
// The size might be zero; if so, make it at least 8 so that we don't have a nullptr
|
||||
const size = std.math.max(@intCast(usize, roc__mainForHost_size()), 8);
|
||||
const raw_output = roc_alloc(@intCast(usize, size), @alignOf(u64)).?;
|
||||
var output = @ptrCast([*]u8, raw_output);
|
||||
|
||||
|
@ -120,7 +121,8 @@ fn to_seconds(tms: std.os.timespec) f64 {
|
|||
fn call_the_closure(closure_data_pointer: [*]u8) void {
|
||||
const allocator = std.heap.page_allocator;
|
||||
|
||||
const size = roc__mainForHost_1__Fx_result_size();
|
||||
// The size might be zero; if so, make it at least 8 so that we don't have a nullptr
|
||||
const size = std.math.max(roc__mainForHost_1__Fx_result_size(), 8);
|
||||
const raw_output = allocator.allocAdvanced(u8, @alignOf(u64), @intCast(usize, size), .at_least) catch unreachable;
|
||||
var output = @ptrCast([*]u8, raw_output);
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue