fix problem with empty closures still considered closures

This commit is contained in:
Folkert 2020-12-10 23:33:53 +01:00
parent 84421ad06d
commit f5c267acf4
7 changed files with 71 additions and 15 deletions

View file

@ -1571,4 +1571,28 @@ mod gen_primitives {
i64 i64
); );
} }
#[test]
fn unified_empty_closure() {
// none of the Closure tags will have a payload
// this was not handled correctly in the past
assert_non_opt_evals_to!(
indoc!(
r#"
app "test" provides [ main ] to "./platform"
foo = \{} ->
when A is
A -> (\_ -> 3.14)
B -> (\_ -> 3.14)
main : F64
main =
(foo {}) 0
"#
),
3.14,
f64
);
}
} }

View file

@ -157,7 +157,7 @@ impl Dependencies {
output output
} }
pub fn add_platform_module( pub fn add_effect_module(
&mut self, &mut self,
module_id: ModuleId, module_id: ModuleId,
dependencies: &MutSet<ModuleId>, dependencies: &MutSet<ModuleId>,
@ -1580,7 +1580,7 @@ fn update<'a>(
.constrained .constrained
.insert(module_id, constrained_module); .insert(module_id, constrained_module);
let mut work = state.dependencies.add_platform_module( let mut work = state.dependencies.add_effect_module(
module_id, module_id,
&MutSet::default(), &MutSet::default(),
state.goal_phase, state.goal_phase,

View file

@ -1636,8 +1636,11 @@ fn specialize_external<'a>(
Layout::Struct(field_layouts) => { Layout::Struct(field_layouts) => {
// NOTE closure unions do not store the tag! // NOTE closure unions do not store the tag!
let field_layouts = &field_layouts[1..]; let field_layouts = &field_layouts[1..];
// TODO check for field_layouts.len() == 1 and do a rename in that case?
for (index, (symbol, _variable)) in captured.iter().enumerate() for (index, (symbol, _variable)) in captured.iter().enumerate()
{ {
// TODO therefore should the wrapped here not be RecordOrSingleTagUnion?
let expr = Expr::AccessAtIndex { let expr = Expr::AccessAtIndex {
index: index as _, index: index as _,
field_layouts, field_layouts,

View file

@ -86,6 +86,9 @@ impl<'a> ClosureLayout<'a> {
fn from_tag_union(arena: &'a Bump, tags: &'a [(TagName, &'a [Layout<'a>])]) -> Self { fn from_tag_union(arena: &'a Bump, tags: &'a [(TagName, &'a [Layout<'a>])]) -> Self {
debug_assert!(tags.len() > 1); debug_assert!(tags.len() > 1);
// if the closed-over value is actually a layout, it should be wrapped in a 1-element record
debug_assert!(matches!(tags[0].0, TagName::Closure(_)));
let mut tag_arguments = Vec::with_capacity_in(tags.len(), arena); let mut tag_arguments = Vec::with_capacity_in(tags.len(), arena);
for (_, tag_args) in tags.iter() { for (_, tag_args) in tags.iter() {
@ -117,7 +120,15 @@ impl<'a> ClosureLayout<'a> {
let mut tags = std::vec::Vec::new(); let mut tags = std::vec::Vec::new();
match roc_types::pretty_print::chase_ext_tag_union(subs, closure_var, &mut tags) { match roc_types::pretty_print::chase_ext_tag_union(subs, closure_var, &mut tags) {
Ok(()) | Err((_, Content::FlexVar(_))) if !tags.is_empty() => { Ok(()) | Err((_, Content::FlexVar(_))) if !tags.is_empty() => {
// this is a closure // special-case the `[ Closure1, Closure2, Closure3 ]` case, where none of
// the tags have a payload
let all_no_payload = tags.iter().all(|(_, arguments)| arguments.is_empty());
if all_no_payload {
return Ok(None);
}
// otherwise, this is a closure with a payload
let variant = union_sorted_tags_help(arena, tags, None, subs); let variant = union_sorted_tags_help(arena, tags, None, subs);
use UnionVariant::*; use UnionVariant::*;
@ -147,9 +158,9 @@ impl<'a> ClosureLayout<'a> {
Ok(Some(closure_layout)) Ok(Some(closure_layout))
} }
Wrapped(tags) => { Wrapped(tags) => {
// Wrapped(Vec<'a, (TagName, &'a [Layout<'a>])>),
let closure_layout = let closure_layout =
ClosureLayout::from_tag_union(arena, tags.into_bump_slice()); ClosureLayout::from_tag_union(arena, tags.into_bump_slice());
Ok(Some(closure_layout)) Ok(Some(closure_layout))
} }
} }
@ -266,7 +277,13 @@ impl<'a> ClosureLayout<'a> {
} }
_ => { _ => {
debug_assert_eq!(symbols.len(), 1); debug_assert_eq!(
symbols.len(),
1,
"symbols {:?} for layout {:?}",
&symbols,
&self.layout
);
Err(symbols[0]) Err(symbols[0])
} }

View file

@ -1,10 +1,20 @@
app "effect-example" app "effect-example"
packages { base: "thing/platform-dir" } packages { base: "thing/platform-dir" }
imports [ base.Task.{ Task, after } ] imports [ base.Task ]
provides [ main ] to base provides [ main ] to base
main : Task.Task {} F64 # main : Task.Task {} F64
main =
Task.after (Task.putLine "foo") \{} ->
Task.putLine "bar"
main =
Task.after (Task.putLine "foo") \{} -> Task.putLine "bar"
# main =
# # Task.after (Task.putLine "foo") \{} -> Task.putLine "bar"
# y = Task.ealways 3.14
#
# a = y |> Task.emap (\x -> x)
# b = y |> Task.emap (\x -> x)
#
# if 1 == 1 then a else b

View file

@ -6,10 +6,12 @@ platform folkertdev/foo
provides [ mainForHost ] provides [ mainForHost ]
effects Effect effects Effect
{ {
putChar : I64 -> Effect {}, putLine : Str -> Effect {}
putLine : Str -> Effect {},
getLine : Effect Str
} }
# putChar : I64 -> Effect {},
# getLine : Effect Str
mainForHost : Task.Task {} F64 as Fx mainForHost : Task.Task {} F64 as Fx
mainForHost = main mainForHost = main

View file

@ -1,5 +1,5 @@
interface Task interface Task
exposes [ Task, putLine, after, always, map, after, fail ] exposes [ Task, putLine, after, always, map, after, fail]
imports [ Effect ] imports [ Effect ]
Task a err : Effect.Effect (Result a err) Task a err : Effect.Effect (Result a err)