Merge branch 'main' into i5318

Signed-off-by: Ayaz <20735482+ayazhafiz@users.noreply.github.com>
This commit is contained in:
Ayaz 2023-05-01 18:47:56 -05:00 committed by GitHub
commit 19c04c69c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 574 additions and 64 deletions

View file

@ -698,7 +698,7 @@ fn solve(
// after a LetCon, we must check if any of the variables that we introduced // after a LetCon, we must check if any of the variables that we introduced
// loop back to themselves after solving the ret_constraint // loop back to themselves after solving the ret_constraint
for (symbol, loc_var) in def_vars.iter() { for (symbol, loc_var) in def_vars.iter() {
check_for_infinite_type(subs, problems, *symbol, *loc_var); check_for_infinite_type(subs, pools, problems, *symbol, *loc_var);
} }
continue; continue;
@ -1331,7 +1331,7 @@ fn solve(
*type_index, *type_index,
); );
open_tag_union(subs, actual); open_tag_union(subs, pools, actual);
state state
} }
@ -1571,7 +1571,7 @@ fn solve(
let almost_eq_snapshot = subs.snapshot(); let almost_eq_snapshot = subs.snapshot();
// TODO: turn this on for bidirectional exhaustiveness checking // TODO: turn this on for bidirectional exhaustiveness checking
// open_tag_union(subs, real_var); // open_tag_union(subs, real_var);
open_tag_union(subs, branches_var); open_tag_union(subs, pools, branches_var);
let almost_eq = matches!( let almost_eq = matches!(
unify( unify(
&mut UEnv::new(subs), &mut UEnv::new(subs),
@ -1901,7 +1901,7 @@ fn compact_lambdas_and_check_obligations(
awaiting_specialization.union(new_awaiting); awaiting_specialization.union(new_awaiting);
} }
fn open_tag_union(subs: &mut Subs, var: Variable) { fn open_tag_union(subs: &mut Subs, pools: &mut Pools, var: Variable) {
let mut stack = vec![var]; let mut stack = vec![var];
while let Some(var) = stack.pop() { while let Some(var) = stack.pop() {
use {Content::*, FlatType::*}; use {Content::*, FlatType::*};
@ -1910,9 +1910,9 @@ fn open_tag_union(subs: &mut Subs, var: Variable) {
match desc.content { match desc.content {
Structure(TagUnion(tags, ext)) => { Structure(TagUnion(tags, ext)) => {
if let Structure(EmptyTagUnion) = subs.get_content_without_compacting(ext.var()) { if let Structure(EmptyTagUnion) = subs.get_content_without_compacting(ext.var()) {
let new_ext = TagExt::Any(subs.fresh_unnamed_flex_var()); let new_ext_var = register(subs, desc.rank, pools, Content::FlexVar(None));
subs.set_rank(new_ext.var(), desc.rank);
let new_union = Structure(TagUnion(tags, new_ext)); let new_union = Structure(TagUnion(tags, TagExt::Any(new_ext_var)));
subs.set_content(var, new_union); subs.set_content(var, new_union);
} }
@ -3592,6 +3592,7 @@ fn create_union_lambda(
fn check_for_infinite_type( fn check_for_infinite_type(
subs: &mut Subs, subs: &mut Subs,
pools: &mut Pools,
problems: &mut Vec<TypeError>, problems: &mut Vec<TypeError>,
symbol: Symbol, symbol: Symbol,
loc_var: Loc<Variable>, loc_var: Loc<Variable>,
@ -3604,7 +3605,9 @@ fn check_for_infinite_type(
for &var in chain.iter().rev() { for &var in chain.iter().rev() {
match *subs.get_content_without_compacting(var) { match *subs.get_content_without_compacting(var) {
Content::Structure(FlatType::TagUnion(tags, ext_var)) => { Content::Structure(FlatType::TagUnion(tags, ext_var)) => {
subs.mark_tag_union_recursive(var, tags, ext_var); let rec_var = subs.mark_tag_union_recursive(var, tags, ext_var);
register_to_pools(subs, rec_var, pools);
continue 'next_occurs_check; continue 'next_occurs_check;
} }
Content::LambdaSet(subs::LambdaSet { Content::LambdaSet(subs::LambdaSet {
@ -3613,12 +3616,14 @@ fn check_for_infinite_type(
unspecialized, unspecialized,
ambient_function: ambient_function_var, ambient_function: ambient_function_var,
}) => { }) => {
subs.mark_lambda_set_recursive( let rec_var = subs.mark_lambda_set_recursive(
var, var,
solved, solved,
unspecialized, unspecialized,
ambient_function_var, ambient_function_var,
); );
register_to_pools(subs, rec_var, pools);
continue 'next_occurs_check; continue 'next_occurs_check;
} }
_ => { /* fall through */ } _ => { /* fall through */ }
@ -4495,3 +4500,8 @@ fn register_with_known_var(
var var
} }
#[inline(always)]
fn register_to_pools(subs: &Subs, var: Variable, pools: &mut Pools) {
pools.get_mut(subs.get_rank(var)).push(var);
}

View file

@ -1512,6 +1512,7 @@ fn opaque_assign_to_symbol() {
#[test] #[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))] #[cfg(any(feature = "gen-llvm", feature = "gen-wasm", feature = "gen-dev"))]
#[ignore = "Blocked on https://github.com/roc-lang/roc/issues/5354"]
fn issue_2777_default_branch_codegen() { fn issue_2777_default_branch_codegen() {
assert_evals_to!( assert_evals_to!(
indoc!( indoc!(

View file

@ -0,0 +1,202 @@
procedure Bool.1 ():
let Bool.25 : Int1 = false;
ret Bool.25;
procedure Bool.2 ():
let Bool.24 : Int1 = true;
ret Bool.24;
procedure List.188 (List.496, List.189, List.187):
let List.526 : Int1 = CallByName Test.1 List.189;
if List.526 then
let List.528 : {} = Struct {};
let List.527 : [C {}, C {}] = TagId(1) List.528;
ret List.527;
else
let List.525 : {} = Struct {};
let List.524 : [C {}, C {}] = TagId(0) List.525;
ret List.524;
procedure List.23 (#Attr.2, #Attr.3, #Attr.4):
let List.529 : List {[<r>C I64, C List *self], [<r>C I64, C List *self]} = lowlevel ListMap2 { xs: `#Attr.#arg1`, ys: `#Attr.#arg2` } #Attr.2 #Attr.3 Test.15 #Attr.4;
decref #Attr.3;
decref #Attr.2;
ret List.529;
procedure List.56 (List.186, List.187):
let List.505 : {} = Struct {};
let List.497 : [C {}, C {}] = CallByName List.92 List.186 List.505 List.187;
let List.502 : U8 = 1i64;
let List.503 : U8 = GetTagId List.497;
let List.504 : Int1 = lowlevel Eq List.502 List.503;
if List.504 then
let List.498 : Int1 = CallByName Bool.2;
ret List.498;
else
let List.499 : Int1 = CallByName Bool.1;
ret List.499;
procedure List.6 (#Attr.2):
let List.495 : U64 = lowlevel ListLen #Attr.2;
ret List.495;
procedure List.6 (#Attr.2):
let List.523 : U64 = lowlevel ListLen #Attr.2;
ret List.523;
procedure List.66 (#Attr.2, #Attr.3):
let List.522 : {[<r>C I64, C List *self], [<r>C I64, C List *self]} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.522;
procedure List.80 (List.534, List.535, List.536, List.537, List.538):
joinpoint List.510 List.433 List.434 List.435 List.436 List.437:
let List.512 : Int1 = CallByName Num.22 List.436 List.437;
if List.512 then
let List.521 : {[<r>C I64, C List *self], [<r>C I64, C List *self]} = CallByName List.66 List.433 List.436;
inc List.521;
let List.513 : [C {}, C {}] = CallByName List.188 List.434 List.521 List.435;
let List.518 : U8 = 1i64;
let List.519 : U8 = GetTagId List.513;
let List.520 : Int1 = lowlevel Eq List.518 List.519;
if List.520 then
let List.438 : {} = UnionAtIndex (Id 1) (Index 0) List.513;
let List.516 : U64 = 1i64;
let List.515 : U64 = CallByName Num.19 List.436 List.516;
jump List.510 List.433 List.438 List.435 List.515 List.437;
else
dec List.433;
let List.439 : {} = UnionAtIndex (Id 0) (Index 0) List.513;
let List.517 : [C {}, C {}] = TagId(0) List.439;
ret List.517;
else
dec List.433;
let List.511 : [C {}, C {}] = TagId(1) List.434;
ret List.511;
in
jump List.510 List.534 List.535 List.536 List.537 List.538;
procedure List.92 (List.430, List.431, List.432):
let List.508 : U64 = 0i64;
let List.509 : U64 = CallByName List.6 List.430;
let List.507 : [C {}, C {}] = CallByName List.80 List.430 List.431 List.432 List.508 List.509;
ret List.507;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.280 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.280;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.278 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.278;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.281 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.281;
procedure Test.1 (Test.77):
joinpoint Test.26 Test.6:
let Test.65 : [<r>C I64, C List *self] = StructAtIndex 1 Test.6;
let Test.66 : U8 = 0i64;
let Test.67 : U8 = GetTagId Test.65;
let Test.68 : Int1 = lowlevel Eq Test.66 Test.67;
if Test.68 then
let Test.57 : [<r>C I64, C List *self] = StructAtIndex 0 Test.6;
inc Test.57;
let Test.58 : U8 = 0i64;
let Test.59 : U8 = GetTagId Test.57;
let #Derived_gen.1 : [<r>C I64, C List *self] = Reset { symbol: Test.57, id: UpdateModeId { id: 1 } };
let Test.60 : Int1 = lowlevel Eq Test.58 Test.59;
if Test.60 then
decref #Derived_gen.1;
let Test.50 : [<r>C I64, C List *self] = StructAtIndex 0 Test.6;
let Test.8 : I64 = UnionAtIndex (Id 0) (Index 0) Test.50;
let Test.49 : [<r>C I64, C List *self] = StructAtIndex 1 Test.6;
dec Test.50;
let Test.10 : I64 = UnionAtIndex (Id 0) (Index 0) Test.49;
joinpoint #Derived_gen.7:
let Test.27 : Int1 = CallByName Num.22 Test.8 Test.10;
ret Test.27;
in
let #Derived_gen.8 : Int1 = lowlevel RefCountIsUnique Test.49;
if #Derived_gen.8 then
decref Test.49;
jump #Derived_gen.7;
else
decref Test.49;
jump #Derived_gen.7;
else
let Test.39 : [<r>C I64, C List *self] = StructAtIndex 0 Test.6;
let Test.42 : [<r>C I64, C List *self] = StructAtIndex 1 Test.6;
let Test.41 : List [<r>C I64, C List *self] = Array [Test.42];
let Test.40 : [<r>C I64, C List *self] = Reuse #Derived_gen.1 UpdateModeId { id: 1 } TagId(1) Test.41;
let Test.38 : {[<r>C I64, C List *self], [<r>C I64, C List *self]} = Struct {Test.39, Test.40};
jump Test.26 Test.38;
else
let Test.61 : [<r>C I64, C List *self] = StructAtIndex 0 Test.6;
inc Test.61;
let Test.62 : U8 = 1i64;
let Test.63 : U8 = GetTagId Test.61;
let #Derived_gen.4 : [<r>C I64, C List *self] = Reset { symbol: Test.61, id: UpdateModeId { id: 4 } };
let Test.64 : Int1 = lowlevel Eq Test.62 Test.63;
if Test.64 then
decref #Derived_gen.4;
let Test.52 : [<r>C I64, C List *self] = StructAtIndex 0 Test.6;
let Test.12 : List [<r>C I64, C List *self] = UnionAtIndex (Id 1) (Index 0) Test.52;
inc Test.12;
let Test.51 : [<r>C I64, C List *self] = StructAtIndex 1 Test.6;
dec Test.52;
let Test.14 : List [<r>C I64, C List *self] = UnionAtIndex (Id 1) (Index 0) Test.51;
joinpoint #Derived_gen.9:
let Test.35 : {} = Struct {};
inc Test.12;
inc Test.14;
let Test.33 : List {[<r>C I64, C List *self], [<r>C I64, C List *self]} = CallByName List.23 Test.12 Test.14 Test.35;
let Test.34 : {} = Struct {};
let Test.29 : Int1 = CallByName List.56 Test.33 Test.34;
if Test.29 then
let Test.31 : U64 = CallByName List.6 Test.12;
dec Test.12;
let Test.32 : U64 = CallByName List.6 Test.14;
dec Test.14;
let Test.30 : Int1 = CallByName Num.22 Test.31 Test.32;
ret Test.30;
else
dec Test.12;
dec Test.14;
let Test.28 : Int1 = CallByName Bool.1;
ret Test.28;
in
let #Derived_gen.10 : Int1 = lowlevel RefCountIsUnique Test.51;
if #Derived_gen.10 then
decref Test.51;
jump #Derived_gen.9;
else
inc Test.14;
decref Test.51;
jump #Derived_gen.9;
else
let Test.48 : [<r>C I64, C List *self] = StructAtIndex 0 Test.6;
let Test.47 : List [<r>C I64, C List *self] = Array [Test.48];
let Test.45 : [<r>C I64, C List *self] = Reuse #Derived_gen.4 UpdateModeId { id: 4 } TagId(1) Test.47;
let Test.46 : [<r>C I64, C List *self] = StructAtIndex 1 Test.6;
let Test.44 : {[<r>C I64, C List *self], [<r>C I64, C List *self]} = Struct {Test.45, Test.46};
jump Test.26 Test.44;
in
jump Test.26 Test.77;
procedure Test.15 (Test.16, Test.17):
let Test.36 : {[<r>C I64, C List *self], [<r>C I64, C List *self]} = Struct {Test.16, Test.17};
ret Test.36;
procedure Test.0 ():
let Test.76 : I64 = 10i64;
let Test.75 : [<r>C I64, C List *self] = TagId(0) Test.76;
let Test.74 : List [<r>C I64, C List *self] = Array [Test.75];
let Test.69 : [<r>C I64, C List *self] = TagId(1) Test.74;
let Test.73 : I64 = 20i64;
let Test.72 : [<r>C I64, C List *self] = TagId(0) Test.73;
let Test.71 : List [<r>C I64, C List *self] = Array [Test.72];
let Test.70 : [<r>C I64, C List *self] = TagId(1) Test.71;
let Test.25 : {[<r>C I64, C List *self], [<r>C I64, C List *self]} = Struct {Test.69, Test.70};
let Test.24 : Int1 = CallByName Test.1 Test.25;
ret Test.24;

View file

@ -2889,6 +2889,30 @@ fn layout_cache_structure_with_multiple_recursive_structures() {
) )
} }
#[mono_test]
fn issue_4770() {
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
isCorrectOrder { left: IsList [IsInteger 10], right: IsList [IsInteger 20] }
isCorrectOrder = \pair ->
when pair is
{ left: IsInteger left, right: IsInteger right } -> left < right
{ left: IsList l, right: IsList r } ->
if List.map2 l r (\left, right -> { left, right }) |> List.all isCorrectOrder then
List.len l < List.len r
else
Bool.false
{ left: IsList _, right: IsInteger _ } -> isCorrectOrder { left: pair.left, right: IsList [pair.right] }
{ left: IsInteger _, right: IsList _ } -> isCorrectOrder { left: IsList [pair.left], right: pair.right }
"#
)
}
#[mono_test(allow_type_errors = "true")] #[mono_test(allow_type_errors = "true")]
fn error_on_erroneous_condition() { fn error_on_erroneous_condition() {
indoc!( indoc!(
@ -2898,4 +2922,4 @@ fn error_on_erroneous_condition() {
main = if True then 1 else 2 main = if True then 1 else 2
"# "#
) )
} }

View file

@ -257,6 +257,7 @@ pub struct InferOptions {
pub allow_errors: bool, pub allow_errors: bool,
pub print_can_decls: bool, pub print_can_decls: bool,
pub print_only_under_alias: bool, pub print_only_under_alias: bool,
pub print_ranks: bool,
pub no_promote: bool, pub no_promote: bool,
} }
@ -465,6 +466,7 @@ impl<'a> QueryCtx<'a> {
DebugPrint { DebugPrint {
print_lambda_sets: true, print_lambda_sets: true,
print_only_under_alias: self.options.print_only_under_alias, print_only_under_alias: self.options.print_only_under_alias,
print_ranks: self.options.print_ranks,
ignore_polarity: true, ignore_polarity: true,
print_weakened_vars: true, print_weakened_vars: true,
}, },

View file

@ -59,6 +59,7 @@ macro_rules! write_parens {
pub struct DebugPrint { pub struct DebugPrint {
pub print_lambda_sets: bool, pub print_lambda_sets: bool,
pub print_only_under_alias: bool, pub print_only_under_alias: bool,
pub print_ranks: bool,
pub ignore_polarity: bool, pub ignore_polarity: bool,
pub print_weakened_vars: bool, pub print_weakened_vars: bool,
} }
@ -67,6 +68,7 @@ impl DebugPrint {
pub const NOTHING: DebugPrint = DebugPrint { pub const NOTHING: DebugPrint = DebugPrint {
print_lambda_sets: false, print_lambda_sets: false,
print_only_under_alias: false, print_only_under_alias: false,
print_ranks: false,
ignore_polarity: false, ignore_polarity: false,
print_weakened_vars: false, print_weakened_vars: false,
}; };
@ -653,6 +655,10 @@ fn write_content<'a>(
) { ) {
use crate::subs::Content::*; use crate::subs::Content::*;
if env.debug.print_ranks {
buf.push_str(&format!("⟨@{:?}", subs.get_rank(var).into_usize()));
}
match subs.get_content_without_compacting(var) { match subs.get_content_without_compacting(var) {
FlexVar(Some(name_index)) => { FlexVar(Some(name_index)) => {
let name = &subs.field_names[name_index.index as usize]; let name = &subs.field_names[name_index.index as usize];

View file

@ -2010,22 +2010,35 @@ impl Subs {
result result
} }
pub fn mark_tag_union_recursive(&mut self, recursive: Variable, tags: UnionTags, ext: TagExt) { /// Returns the new recursion variable, which should be introduced to the environment as
/// appropriate.
#[must_use]
pub fn mark_tag_union_recursive(
&mut self,
recursive: Variable,
tags: UnionTags,
ext: TagExt,
) -> Variable {
let (rec_var, new_tags) = self.mark_union_recursive_help(recursive, tags); let (rec_var, new_tags) = self.mark_union_recursive_help(recursive, tags);
let new_ext = ext.map(|v| self.explicit_substitute(recursive, rec_var, v)); let new_ext = ext.map(|v| self.explicit_substitute(recursive, rec_var, v));
let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, new_ext); let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, new_ext);
self.set_content(recursive, Content::Structure(flat_type)); self.set_content(recursive, Content::Structure(flat_type));
rec_var
} }
/// Returns the new recursion variable, which should be introduced to the environment as
/// appropriate.
#[must_use]
pub fn mark_lambda_set_recursive( pub fn mark_lambda_set_recursive(
&mut self, &mut self,
recursive: Variable, recursive: Variable,
solved_lambdas: UnionLambdas, solved_lambdas: UnionLambdas,
unspecialized_lambdas: SubsSlice<Uls>, unspecialized_lambdas: SubsSlice<Uls>,
ambient_function_var: Variable, ambient_function_var: Variable,
) { ) -> Variable {
let (rec_var, new_tags) = self.mark_union_recursive_help(recursive, solved_lambdas); let (rec_var, new_tags) = self.mark_union_recursive_help(recursive, solved_lambdas);
let new_lambda_set = Content::LambdaSet(LambdaSet { let new_lambda_set = Content::LambdaSet(LambdaSet {
@ -2036,6 +2049,8 @@ impl Subs {
}); });
self.set_content(recursive, new_lambda_set); self.set_content(recursive, new_lambda_set);
rec_var
} }
fn mark_union_recursive_help<L: Label>( fn mark_union_recursive_help<L: Label>(

View file

@ -256,6 +256,7 @@ impl<'a> TestCase<'a> {
match opt.trim() { match opt.trim() {
"allow_errors" => infer_opts.allow_errors = true, "allow_errors" => infer_opts.allow_errors = true,
"print_only_under_alias" => infer_opts.print_only_under_alias = true, "print_only_under_alias" => infer_opts.print_only_under_alias = true,
"print_ranks" => infer_opts.print_ranks = true,
other => return Err(format!("unknown infer option: {other:?}").into()), other => return Err(format!("unknown infer option: {other:?}").into()),
} }
} }

View file

@ -3,6 +3,6 @@ app "test" provides [main] to "./platform"
main = main =
\x -> when x is \x -> when x is
#^ [A [B]w_a [C]w_b] #^ [A [B]* [C]*]
A B _ -> "" A B _ -> ""
A _ C -> "" A _ C -> ""

View file

@ -2,6 +2,6 @@ app "test" provides [main] to "./platform"
main = main =
\x -> when x is \x -> when x is
#^ { a : [A { b : [B]w_a }*]w_b }* #^ { a : [A { b : [B]* }*]* }*
{ a: A { b: B } } -> "" { a: A { b: B } } -> ""
_ -> "" _ -> ""

View file

@ -0,0 +1,12 @@
app "test" provides [main] to "./platform"
main =
isCorrectOrder (IsList [IsStr ""])
# ^^^^^^^^^^^^^^ [IsList (List a), IsStr Str]w_b as a -[[isCorrectOrder(1)]]-> Bool
isCorrectOrder = \pair ->
#^^^^^^^^^^^^^^{-1} [IsList (List a)]* as a -[[isCorrectOrder(1)]]-> Bool
when pair is
IsList l -> List.all l isCorrectOrder
_ -> Bool.false

View file

@ -0,0 +1,10 @@
app "test" provides [main] to "./platform"
main = isCorrectOrder (IsList [IsStr ""])
# ^^^^^^^^^^^^^^ [IsList (List a), IsStr Str] as a -[[isCorrectOrder(1)]]-> Bool
isCorrectOrder = \pair ->
#^^^^^^^^^^^^^^{-1} [IsList (List a), IsStr *] as a -[[isCorrectOrder(1)]]-> Bool
when pair is
IsList l -> List.all l isCorrectOrder
IsStr _ -> isCorrectOrder (IsList [pair])

View file

@ -1,6 +1,6 @@
app "test" provides [getInfallible] to "./platform" app "test" provides [getInfallible] to "./platform"
getInfallible = \result -> when result is getInfallible = \result -> when result is
#^^^^^^^^^^^^^{-1} [Ok a]w_b -[[getInfallible(0)]]-> a #^^^^^^^^^^^^^{-1} [Ok a]* -[[getInfallible(0)]]-> a
Ok x -> x Ok x -> x
_ -> crash "turns out this was fallible" _ -> crash "turns out this was fallible"

View file

@ -1449,8 +1449,8 @@ fn separate_union_lambdas<M: MetaCollector>(
// code generator, we have not yet observed a case where they must // code generator, we have not yet observed a case where they must
// collapsed to the type checker of the surface syntax. // collapsed to the type checker of the surface syntax.
// It is possible this assumption will be invalidated! // It is possible this assumption will be invalidated!
maybe_mark_union_recursive(env, var1); maybe_mark_union_recursive(env, pool, var1);
maybe_mark_union_recursive(env, var2); maybe_mark_union_recursive(env, pool, var2);
} }
// Check whether the two type variables in the closure set are // Check whether the two type variables in the closure set are
@ -2721,7 +2721,6 @@ fn unify_tag_unions<M: MetaCollector>(
initial_ext1: TagExt, initial_ext1: TagExt,
tags2: UnionTags, tags2: UnionTags,
initial_ext2: TagExt, initial_ext2: TagExt,
recursion_var: Rec,
) -> Outcome<M> { ) -> Outcome<M> {
let (separate, mut ext1, mut ext2) = let (separate, mut ext1, mut ext2) =
separate_union_tags(env.subs, tags1, initial_ext1, tags2, initial_ext2); separate_union_tags(env.subs, tags1, initial_ext1, tags2, initial_ext2);
@ -2781,7 +2780,6 @@ fn unify_tag_unions<M: MetaCollector>(
shared_tags, shared_tags,
OtherTags2::Empty, OtherTags2::Empty,
merge_tag_exts(ext1, ext2), merge_tag_exts(ext1, ext2),
recursion_var,
); );
shared_tags_outcome.union(ext_outcome); shared_tags_outcome.union(ext_outcome);
@ -2818,15 +2816,8 @@ fn unify_tag_unions<M: MetaCollector>(
let combined_ext = ext1.map(|_| extra_tags_in_2); let combined_ext = ext1.map(|_| extra_tags_in_2);
let mut shared_tags_outcome = unify_shared_tags( let mut shared_tags_outcome =
env, unify_shared_tags(env, pool, ctx, shared_tags, OtherTags2::Empty, combined_ext);
pool,
ctx,
shared_tags,
OtherTags2::Empty,
combined_ext,
recursion_var,
);
shared_tags_outcome.union(ext_outcome); shared_tags_outcome.union(ext_outcome);
@ -2881,15 +2872,8 @@ fn unify_tag_unions<M: MetaCollector>(
let combined_ext = ext2.map(|_| extra_tags_in_1); let combined_ext = ext2.map(|_| extra_tags_in_1);
let shared_tags_outcome = unify_shared_tags( let shared_tags_outcome =
env, unify_shared_tags(env, pool, ctx, shared_tags, OtherTags2::Empty, combined_ext);
pool,
ctx,
shared_tags,
OtherTags2::Empty,
combined_ext,
recursion_var,
);
total_outcome.union(shared_tags_outcome); total_outcome.union(shared_tags_outcome);
if extend_ext_with_uninhabited { if extend_ext_with_uninhabited {
@ -2954,8 +2938,7 @@ fn unify_tag_unions<M: MetaCollector>(
env.subs.commit_snapshot(snapshot); env.subs.commit_snapshot(snapshot);
let shared_tags_outcome = let shared_tags_outcome = unify_shared_tags(env, pool, ctx, shared_tags, other_tags, ext);
unify_shared_tags(env, pool, ctx, shared_tags, other_tags, ext, recursion_var);
total_outcome.union(shared_tags_outcome); total_outcome.union(shared_tags_outcome);
total_outcome total_outcome
} }
@ -2972,7 +2955,7 @@ enum OtherTags2 {
/// Promotes a non-recursive tag union or lambda set to its recursive variant, if it is found to be /// Promotes a non-recursive tag union or lambda set to its recursive variant, if it is found to be
/// recursive. /// recursive.
fn maybe_mark_union_recursive(env: &mut Env, union_var: Variable) { fn maybe_mark_union_recursive(env: &mut Env, pool: &mut Pool, union_var: Variable) {
let subs = &mut env.subs; let subs = &mut env.subs;
'outer: while let Err((_, chain)) = subs.occurs(union_var) { 'outer: while let Err((_, chain)) = subs.occurs(union_var) {
// walk the chain till we find a tag union or lambda set, starting from the variable that // walk the chain till we find a tag union or lambda set, starting from the variable that
@ -2981,7 +2964,9 @@ fn maybe_mark_union_recursive(env: &mut Env, union_var: Variable) {
let description = subs.get(v); let description = subs.get(v);
match description.content { match description.content {
Content::Structure(FlatType::TagUnion(tags, ext_var)) => { Content::Structure(FlatType::TagUnion(tags, ext_var)) => {
subs.mark_tag_union_recursive(v, tags, ext_var); let rec_var = subs.mark_tag_union_recursive(v, tags, ext_var);
pool.push(rec_var);
continue 'outer; continue 'outer;
} }
LambdaSet(self::LambdaSet { LambdaSet(self::LambdaSet {
@ -2990,7 +2975,14 @@ fn maybe_mark_union_recursive(env: &mut Env, union_var: Variable) {
unspecialized, unspecialized,
ambient_function: ambient_function_var, ambient_function: ambient_function_var,
}) => { }) => {
subs.mark_lambda_set_recursive(v, solved, unspecialized, ambient_function_var); let rec_var = subs.mark_lambda_set_recursive(
v,
solved,
unspecialized,
ambient_function_var,
);
pool.push(rec_var);
continue 'outer; continue 'outer;
} }
_ => { /* fall through */ } _ => { /* fall through */ }
@ -3066,6 +3058,24 @@ fn choose_merged_var(subs: &Subs, var1: Variable, var2: Variable) -> Variable {
} }
} }
#[inline]
fn find_union_rec(subs: &Subs, ctx: &Context) -> Rec {
match (
subs.get_content_without_compacting(ctx.first),
subs.get_content_without_compacting(ctx.second),
) {
(Structure(s1), Structure(s2)) => match (s1, s2) {
(FlatType::RecursiveTagUnion(l, _, _), FlatType::RecursiveTagUnion(r, _, _)) => {
Rec::Both(*l, *r)
}
(FlatType::RecursiveTagUnion(l, _, _), _) => Rec::Left(*l),
(_, FlatType::RecursiveTagUnion(r, _, _)) => Rec::Right(*r),
_ => Rec::None,
},
_ => Rec::None,
}
}
#[must_use] #[must_use]
fn unify_shared_tags<M: MetaCollector>( fn unify_shared_tags<M: MetaCollector>(
env: &mut Env, env: &mut Env,
@ -3074,7 +3084,6 @@ fn unify_shared_tags<M: MetaCollector>(
shared_tags: Vec<(TagName, (VariableSubsSlice, VariableSubsSlice))>, shared_tags: Vec<(TagName, (VariableSubsSlice, VariableSubsSlice))>,
other_tags: OtherTags2, other_tags: OtherTags2,
ext: TagExt, ext: TagExt,
recursion_var: Rec,
) -> Outcome<M> { ) -> Outcome<M> {
let mut matching_tags = Vec::default(); let mut matching_tags = Vec::default();
let num_shared_tags = shared_tags.len(); let num_shared_tags = shared_tags.len();
@ -3119,8 +3128,8 @@ fn unify_shared_tags<M: MetaCollector>(
// since we're expanding tag unions to equal depths as described above, // since we're expanding tag unions to equal depths as described above,
// we'll always pass through this branch. So, we promote tag unions to recursive // we'll always pass through this branch. So, we promote tag unions to recursive
// ones here if it turns out they are that. // ones here if it turns out they are that.
maybe_mark_union_recursive(env, actual); maybe_mark_union_recursive(env, pool, actual);
maybe_mark_union_recursive(env, expected); maybe_mark_union_recursive(env, pool, expected);
let mut outcome = Outcome::<M>::default(); let mut outcome = Outcome::<M>::default();
@ -3183,6 +3192,13 @@ fn unify_shared_tags<M: MetaCollector>(
} }
}; };
// Look up if either unions are recursive, and if so, what the recursive variable is.
//
// We wait until we're about to merge the unions to do this, since above, while unifying
// payloads, we may have promoted a non-recursive union involved in this unification to
// a recursive one.
let recursion_var = find_union_rec(env.subs, ctx);
let merge_outcome = unify_shared_tags_merge(env, ctx, new_tags, new_ext_var, recursion_var); let merge_outcome = unify_shared_tags_merge(env, ctx, new_tags, new_ext_var, recursion_var);
total_outcome.union(merge_outcome); total_outcome.union(merge_outcome);
@ -3266,24 +3282,20 @@ fn unify_flat_type<M: MetaCollector>(
} }
(TagUnion(tags1, ext1), TagUnion(tags2, ext2)) => { (TagUnion(tags1, ext1), TagUnion(tags2, ext2)) => {
unify_tag_unions(env, pool, ctx, *tags1, *ext1, *tags2, *ext2, Rec::None) unify_tag_unions(env, pool, ctx, *tags1, *ext1, *tags2, *ext2)
} }
(RecursiveTagUnion(recursion_var, tags1, ext1), TagUnion(tags2, ext2)) => { (RecursiveTagUnion(recursion_var, tags1, ext1), TagUnion(tags2, ext2)) => {
debug_assert!(is_recursion_var(env.subs, *recursion_var)); debug_assert!(is_recursion_var(env.subs, *recursion_var));
// this never happens in type-correct programs, but may happen if there is a type error // this never happens in type-correct programs, but may happen if there is a type error
let rec = Rec::Left(*recursion_var); unify_tag_unions(env, pool, ctx, *tags1, *ext1, *tags2, *ext2)
unify_tag_unions(env, pool, ctx, *tags1, *ext1, *tags2, *ext2, rec)
} }
(TagUnion(tags1, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => { (TagUnion(tags1, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => {
debug_assert!(is_recursion_var(env.subs, *recursion_var)); debug_assert!(is_recursion_var(env.subs, *recursion_var));
let rec = Rec::Right(*recursion_var); unify_tag_unions(env, pool, ctx, *tags1, *ext1, *tags2, *ext2)
unify_tag_unions(env, pool, ctx, *tags1, *ext1, *tags2, *ext2, rec)
} }
(RecursiveTagUnion(rec1, tags1, ext1), RecursiveTagUnion(rec2, tags2, ext2)) => { (RecursiveTagUnion(rec1, tags1, ext1), RecursiveTagUnion(rec2, tags2, ext2)) => {
@ -3298,8 +3310,7 @@ fn unify_flat_type<M: MetaCollector>(
env.subs.dbg(*rec2) env.subs.dbg(*rec2)
); );
let rec = Rec::Both(*rec1, *rec2); let mut outcome = unify_tag_unions(env, pool, ctx, *tags1, *ext1, *tags2, *ext2);
let mut outcome = unify_tag_unions(env, pool, ctx, *tags1, *ext1, *tags2, *ext2, rec);
outcome.union(unify_pool(env, pool, *rec1, *rec2, ctx.mode)); outcome.union(unify_pool(env, pool, *rec1, *rec2, ctx.mode));
outcome outcome
@ -3398,7 +3409,7 @@ fn unify_flat_type<M: MetaCollector>(
); );
let tags2 = UnionTags::from_slices(*tag_names, empty_tag_var_slices); let tags2 = UnionTags::from_slices(*tag_names, empty_tag_var_slices);
unify_tag_unions(env, pool, ctx, *tags1, *ext1, tags2, *ext2, Rec::None) unify_tag_unions(env, pool, ctx, *tags1, *ext1, tags2, *ext2)
} }
(FunctionOrTagUnion(tag_names, _, ext1), TagUnion(tags2, ext2)) => { (FunctionOrTagUnion(tag_names, _, ext1), TagUnion(tags2, ext2)) => {
let empty_tag_var_slices = SubsSlice::extend_new( let empty_tag_var_slices = SubsSlice::extend_new(
@ -3407,7 +3418,7 @@ fn unify_flat_type<M: MetaCollector>(
); );
let tags1 = UnionTags::from_slices(*tag_names, empty_tag_var_slices); let tags1 = UnionTags::from_slices(*tag_names, empty_tag_var_slices);
unify_tag_unions(env, pool, ctx, tags1, *ext1, *tags2, *ext2, Rec::None) unify_tag_unions(env, pool, ctx, tags1, *ext1, *tags2, *ext2)
} }
(RecursiveTagUnion(recursion_var, tags1, ext1), FunctionOrTagUnion(tag_names, _, ext2)) => { (RecursiveTagUnion(recursion_var, tags1, ext1), FunctionOrTagUnion(tag_names, _, ext2)) => {
@ -3419,9 +3430,8 @@ fn unify_flat_type<M: MetaCollector>(
std::iter::repeat(Default::default()).take(tag_names.len()), std::iter::repeat(Default::default()).take(tag_names.len()),
); );
let tags2 = UnionTags::from_slices(*tag_names, empty_tag_var_slices); let tags2 = UnionTags::from_slices(*tag_names, empty_tag_var_slices);
let rec = Rec::Left(*recursion_var);
unify_tag_unions(env, pool, ctx, *tags1, *ext1, tags2, *ext2, rec) unify_tag_unions(env, pool, ctx, *tags1, *ext1, tags2, *ext2)
} }
(FunctionOrTagUnion(tag_names, _, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => { (FunctionOrTagUnion(tag_names, _, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => {
@ -3432,9 +3442,8 @@ fn unify_flat_type<M: MetaCollector>(
std::iter::repeat(Default::default()).take(tag_names.len()), std::iter::repeat(Default::default()).take(tag_names.len()),
); );
let tags1 = UnionTags::from_slices(*tag_names, empty_tag_var_slices); let tags1 = UnionTags::from_slices(*tag_names, empty_tag_var_slices);
let rec = Rec::Right(*recursion_var);
unify_tag_unions(env, pool, ctx, tags1, *ext1, *tags2, *ext2, rec) unify_tag_unions(env, pool, ctx, tags1, *ext1, *tags2, *ext2)
} }
// these have underscores because they're unused in --release builds // these have underscores because they're unused in --release builds

View file

@ -621,6 +621,9 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
Content::Structure(FlatType::Record(fields, _)) => { Content::Structure(FlatType::Record(fields, _)) => {
struct_to_ast(env, mem, addr, *fields) struct_to_ast(env, mem, addr, *fields)
} }
Content::Structure(FlatType::Tuple(elems,_)) => {
struct_to_ast_tuple(env, mem, addr, *elems)
}
Content::Structure(FlatType::TagUnion(tags, _)) => { Content::Structure(FlatType::TagUnion(tags, _)) => {
debug_assert_eq!(tags.len(), 1); debug_assert_eq!(tags.len(), 1);

View file

@ -1292,3 +1292,15 @@ fn tuple() {
r#"("a", 2) : ( Str, U32 )*"#, r#"("a", 2) : ( Str, U32 )*"#,
); );
} }
#[test]
fn nested_tuple() {
expect_success(
indoc!(
r#"
("a", (2u32, 3u32))
"#
),
r#"("a", (2, 3)) : ( Str, ( U32, U32 )a )a"#,
);
}

View file

@ -1,4 +1,2 @@
build build
node_modules node_modules
hello.js
libhello

View file

@ -1,4 +1,4 @@
platform "typescript-interop" platform "nodejs-interop"
requires {} { main : Str -> Str } requires {} { main : Str -> Str }
exposes [] exposes []
packages {} packages {}

View file

@ -0,0 +1 @@
roc-app.wasm

View file

@ -0,0 +1,12 @@
# NodeJS Interop
This is an example of calling Roc code from [Node.js](https://nodejs.org/en/).
You'll need to have [Zig](https://zig-lang.org) installed. Run this from the current directory:
```
roc build --target=wasm32
node hello.js
```
That's it!

View file

@ -0,0 +1,59 @@
const fs = require('fs/promises');
const { TextDecoder } = require('util');
async function roc_nodejs_platform_run(wasm_filename, callback) {
const decoder = new TextDecoder();
let wasmMemoryBuffer;
let exit_code;
function js_display_roc_string(str_bytes, str_len) {
const utf8_bytes = wasmMemoryBuffer.subarray(str_bytes, str_bytes + str_len);
const js_string = decoder.decode(utf8_bytes);
callback(js_string);
}
const importObj = {
wasi_snapshot_preview1: {
proc_exit: (code) => {
if (code !== 0) {
console.error(`Exited with code ${code}`);
}
exit_code = code;
},
fd_write: (x) => {
console.error(`fd_write not supported: ${x}`);
},
},
env: {
js_display_roc_string,
roc_panic: (_pointer, _tag_id) => {
throw "Roc panicked!";
},
},
};
const module_bytes = await fs.readFile(wasm_filename);
const wasm = await WebAssembly.instantiate(module_bytes, importObj);
wasmMemoryBuffer = new Uint8Array(wasm.instance.exports.memory.buffer);
try {
wasm.instance.exports._start();
} catch (e) {
const is_ok = e.message === "unreachable" && exit_code === 0;
if (!is_ok) {
console.error(e);
}
}
}
if (typeof module !== "undefined") {
module.exports = {
roc_nodejs_platform_run,
};
}
// Run the code with a sample WebAssembly file
roc_nodejs_platform_run('./roc-app.wasm', (output) => {
console.log(output);
});

View file

@ -0,0 +1,7 @@
app "roc-app"
packages { pf: "platform/main.roc" }
imports []
provides [main] to pf
main : Str
main = "Hello from Roc!"

View file

@ -0,0 +1,61 @@
async function roc_web_platform_run(wasm_filename, callback) {
const decoder = new TextDecoder();
let memory_bytes;
let exit_code;
function js_display_roc_string(str_bytes, str_len) {
const utf8_bytes = memory_bytes.subarray(str_bytes, str_bytes + str_len);
const js_string = decoder.decode(utf8_bytes);
callback(js_string);
}
const importObj = {
wasi_snapshot_preview1: {
proc_exit: (code) => {
if (code !== 0) {
console.error(`Exited with code ${code}`);
}
exit_code = code;
},
fd_write: (x) => {
console.error(`fd_write not supported: ${x}`);
},
},
env: {
js_display_roc_string,
roc_panic: (_pointer, _tag_id) => {
throw "Roc panicked!";
},
},
};
const fetchPromise = fetch(wasm_filename);
let wasm;
if (WebAssembly.instantiateStreaming) {
// streaming API has better performance if available
// It can start compiling Wasm before it has fetched all of the bytes, so we don't `await` the request!
wasm = await WebAssembly.instantiateStreaming(fetchPromise, importObj);
} else {
const response = await fetchPromise;
const module_bytes = await response.arrayBuffer();
wasm = await WebAssembly.instantiate(module_bytes, importObj);
}
memory_bytes = new Uint8Array(wasm.instance.exports.memory.buffer);
try {
wasm.instance.exports._start();
} catch (e) {
const is_ok = e.message === "unreachable" && exit_code === 0;
if (!is_ok) {
console.error(e);
}
}
}
if (typeof module !== "undefined") {
module.exports = {
roc_web_platform_run,
};
}

View file

@ -0,0 +1,57 @@
const str = @import("glue").str;
const builtin = @import("builtin");
const RocStr = str.RocStr;
comptime {
if (builtin.target.cpu.arch != .wasm32) {
@compileError("This platform is for WebAssembly only. You need to pass `--target wasm32` to the Roc compiler.");
}
}
const Align = extern struct { a: usize, b: usize };
extern fn malloc(size: usize) callconv(.C) ?*align(@alignOf(Align)) anyopaque;
extern fn realloc(c_ptr: [*]align(@alignOf(Align)) u8, size: usize) callconv(.C) ?*anyopaque;
extern fn free(c_ptr: [*]align(@alignOf(Align)) u8) callconv(.C) void;
extern fn memcpy(dest: *anyopaque, src: *anyopaque, count: usize) *anyopaque;
export fn roc_alloc(size: usize, alignment: u32) callconv(.C) ?*anyopaque {
_ = alignment;
return malloc(size);
}
export fn roc_realloc(c_ptr: *anyopaque, new_size: usize, old_size: usize, alignment: u32) callconv(.C) ?*anyopaque {
_ = old_size;
_ = alignment;
return realloc(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)), new_size);
}
export fn roc_dealloc(c_ptr: *anyopaque, alignment: u32) callconv(.C) void {
_ = alignment;
free(@alignCast(@alignOf(Align), @ptrCast([*]u8, c_ptr)));
}
export fn roc_memcpy(dest: *anyopaque, src: *anyopaque, count: usize) callconv(.C) void {
_ = memcpy(dest, src, count);
}
// NOTE roc_panic is provided in the JS file, so it can throw an exception
extern fn roc__mainForHost_1_exposed(*RocStr) void;
extern fn js_display_roc_string(str_bytes: ?[*]u8, str_len: usize) void;
pub fn main() u8 {
// actually call roc to populate the callresult
var callresult = RocStr.empty();
roc__mainForHost_1_exposed(&callresult);
// display the result using JavaScript
js_display_roc_string(callresult.asU8ptrMut(), callresult.len());
callresult.decref();
return 0;
}

View file

@ -0,0 +1,9 @@
platform "wasm-nodejs-example-platform"
requires {} { main : Str }
exposes []
packages {}
imports []
provides [mainForHost]
mainForHost : Str
mainForHost = main

View file

@ -1 +0,0 @@
# TODO