diff --git a/crates/compiler/mono/src/ir.rs b/crates/compiler/mono/src/ir.rs index 8812e9b897..789c5b8bd6 100644 --- a/crates/compiler/mono/src/ir.rs +++ b/crates/compiler/mono/src/ir.rs @@ -3130,7 +3130,12 @@ fn specialize_external<'a>( tag_id, .. } => { - debug_assert!(matches!(union_layout, UnionLayout::NonRecursive(_))); + debug_assert!(matches!( + union_layout, + UnionLayout::NonRecursive(_) + | UnionLayout::Recursive(_) + | UnionLayout::NullableUnwrapped { .. } + )); debug_assert_eq!(field_layouts.len(), captured.len()); // captured variables are in symbol-alphabetic order, but now we want @@ -3149,7 +3154,9 @@ fn specialize_external<'a>( size2.cmp(&size1) }); - for (index, (symbol, layout)) in combined.iter().enumerate() { + for (index, (symbol, _)) in combined.iter().enumerate() { + let layout = union_layout.layout_at(tag_id, index); + let expr = Expr::UnionAtIndex { tag_id, structure: Symbol::ARG_CLOSURE, @@ -3162,7 +3169,7 @@ fn specialize_external<'a>( specialized_body = Stmt::Let( symbol, expr, - **layout, + layout, env.arena.alloc(specialized_body), ); } diff --git a/crates/compiler/mono/src/layout.rs b/crates/compiler/mono/src/layout.rs index d05c3f3cc4..f0da6b7540 100644 --- a/crates/compiler/mono/src/layout.rs +++ b/crates/compiler/mono/src/layout.rs @@ -10,7 +10,8 @@ use roc_problem::can::RuntimeError; use roc_target::{PtrWidth, TargetInfo}; use roc_types::num::NumericRange; use roc_types::subs::{ - self, Content, FlatType, Label, RecordFields, Subs, UnionTags, UnsortedUnionLabels, Variable, + self, Content, FlatType, Label, OptVariable, RecordFields, Subs, UnionTags, + UnsortedUnionLabels, Variable, }; use roc_types::types::{gather_fields_unsorted_iter, RecordField, RecordFieldsError}; use std::cmp::Ordering; @@ -842,6 +843,7 @@ impl<'a> LambdaSet<'a> { let comparator = |other_name: Symbol, other_captures_layouts: &[Layout]| { other_name == lambda_name.name + // Make sure all captures are equal && other_captures_layouts .iter() .eq(lambda_name.captures_niche.0) @@ -859,14 +861,25 @@ impl<'a> LambdaSet<'a> { debug_assert!(self.contains(function_symbol), "function symbol not in set"); let comparator = |other_name: Symbol, other_captures_layouts: &[Layout]| { - other_name == function_symbol && other_captures_layouts.iter().eq(captures_layouts) + other_name == function_symbol + && other_captures_layouts + .iter() + .zip(captures_layouts) + .all(|(other_layout, layout)| self.capture_layouts_eq(other_layout, layout)) }; let (name, layouts) = self .set .iter() .find(|(name, layouts)| comparator(*name, layouts)) - .expect("no lambda set found"); + .unwrap_or_else(|| { + internal_error!( + "no lambda set found for ({:?}, {:#?}): {:#?}", + function_symbol, + captures_layouts, + self + ) + }); LambdaName { name: *name, @@ -874,6 +887,38 @@ impl<'a> LambdaSet<'a> { } } + /// Checks if two captured layouts are equivalent under the current lambda set. + /// Resolves recursive pointers to the layout of the lambda set. + fn capture_layouts_eq(&self, left: &Layout, right: &Layout) -> bool { + if left == right { + return true; + } + + let left = if left == &Layout::RecursivePointer { + let runtime_repr = self.runtime_representation(); + debug_assert!(matches!( + runtime_repr, + Layout::Union(UnionLayout::Recursive(_) | UnionLayout::NullableUnwrapped { .. }) + )); + Layout::LambdaSet(*self) + } else { + *left + }; + + let right = if right == &Layout::RecursivePointer { + let runtime_repr = self.runtime_representation(); + debug_assert!(matches!( + runtime_repr, + Layout::Union(UnionLayout::Recursive(_) | UnionLayout::NullableUnwrapped { .. }) + )); + Layout::LambdaSet(*self) + } else { + *right + }; + + left == right + } + fn layout_for_member(&self, comparator: F) -> ClosureRepresentation<'a> where F: Fn(Symbol, &[Layout]) -> bool, @@ -902,16 +947,48 @@ impl<'a> LambdaSet<'a> { union_layout: *union, } } - UnionLayout::Recursive(_) => todo!("recursive closures"), + UnionLayout::Recursive(_) => { + let (index, (name, fields)) = self + .set + .iter() + .enumerate() + .find(|(_, (s, layouts))| comparator(*s, layouts)) + .unwrap(); + + let closure_name = *name; + + ClosureRepresentation::Union { + tag_id: index as TagIdIntType, + alphabetic_order_fields: fields, + closure_name, + union_layout: *union, + } + } + UnionLayout::NullableUnwrapped { + nullable_id: _, + other_fields: _, + } => { + let (index, (name, fields)) = self + .set + .iter() + .enumerate() + .find(|(_, (s, layouts))| comparator(*s, layouts)) + .unwrap(); + + let closure_name = *name; + + ClosureRepresentation::Union { + tag_id: index as TagIdIntType, + alphabetic_order_fields: fields, + closure_name, + union_layout: *union, + } + } UnionLayout::NonNullableUnwrapped(_) => todo!("recursive closures"), UnionLayout::NullableWrapped { nullable_id: _, other_tags: _, } => todo!("recursive closures"), - UnionLayout::NullableUnwrapped { - nullable_id: _, - other_fields: _, - } => todo!("recursive closures"), } } Layout::Struct { .. } => { @@ -971,7 +1048,7 @@ impl<'a> LambdaSet<'a> { target_info: TargetInfo, ) -> Result { match resolve_lambda_set(subs, closure_var) { - ResolvedLambdaSet::Set(mut lambdas) => { + ResolvedLambdaSet::Set(mut lambdas, opt_recursion_var) => { // sort the tags; make sure ordering stays intact! lambdas.sort_by_key(|(sym, _)| *sym); @@ -992,6 +1069,9 @@ impl<'a> LambdaSet<'a> { seen: Vec::new_in(arena), target_info, }; + if let Some(rec_var) = opt_recursion_var.into_variable() { + env.insert_seen(rec_var); + } for var in variables { arguments.push(Layout::from_var(&mut env, *var)?); @@ -1048,6 +1128,7 @@ impl<'a> LambdaSet<'a> { arena, subs, set_with_variables, + opt_recursion_var.into_variable(), target_info, )); @@ -1071,10 +1152,28 @@ impl<'a> LambdaSet<'a> { arena: &'a Bump, subs: &Subs, tags: std::vec::Vec<(Symbol, std::vec::Vec)>, + opt_rec_var: Option, target_info: TargetInfo, ) -> Layout<'a> { + if let Some(rec_var) = opt_rec_var { + let tags: std::vec::Vec<_> = tags + .iter() + .map(|(sym, vars)| (sym, vars.as_slice())) + .collect(); + let tags = UnsortedUnionLabels { tags }; + let mut env = Env { + seen: Vec::new_in(arena), + target_info, + arena, + subs, + }; + + return layout_from_recursive_union(&mut env, rec_var, &tags) + .expect("unable to create lambda set representation"); + } + // otherwise, this is a closure with a payload - let variant = union_sorted_tags_help(arena, tags, None, subs, target_info); + let variant = union_sorted_tags_help(arena, tags, opt_rec_var, subs, target_info); use UnionVariant::*; match variant { @@ -1108,7 +1207,12 @@ impl<'a> LambdaSet<'a> { Layout::Union(UnionLayout::NonRecursive(tag_arguments.into_bump_slice())) } - _ => panic!("handle recursive layouts"), + Recursive { .. } + | NullableUnwrapped { .. } + | NullableWrapped { .. } + | NonNullableUnwrapped { .. } => { + internal_error!("Recursive layouts should be produced in an earlier branch") + } } } } @@ -1130,7 +1234,10 @@ impl<'a> LambdaSet<'a> { } enum ResolvedLambdaSet { - Set(std::vec::Vec<(Symbol, std::vec::Vec)>), + Set( + std::vec::Vec<(Symbol, std::vec::Vec)>, + OptVariable, + ), /// TODO: figure out if this can happen in a correct program, or is the result of a bug in our /// compiler. See https://github.com/rtfeldman/roc/issues/3163. Unbound, @@ -1142,7 +1249,7 @@ fn resolve_lambda_set(subs: &Subs, mut var: Variable) -> ResolvedLambdaSet { match subs.get_content_without_compacting(var) { Content::LambdaSet(subs::LambdaSet { solved, - recursion_var: _, + recursion_var, unspecialized, ambient_function: _, }) => { @@ -1153,7 +1260,7 @@ fn resolve_lambda_set(subs: &Subs, mut var: Variable) -> ResolvedLambdaSet { subs.uls_of_var ); roc_types::pretty_print::push_union(subs, solved, &mut set); - return ResolvedLambdaSet::Set(set); + return ResolvedLambdaSet::Set(set, *recursion_var); } Content::RecursionVar { structure, .. } => { var = *structure; @@ -2130,10 +2237,14 @@ fn layout_from_flat_type<'a>( } } Func(_, closure_var, _) => { - let lambda_set = - LambdaSet::from_var(env.arena, env.subs, closure_var, env.target_info)?; + if env.is_seen(closure_var) { + Ok(Layout::RecursivePointer) + } else { + let lambda_set = + LambdaSet::from_var(env.arena, env.subs, closure_var, env.target_info)?; - Ok(Layout::LambdaSet(lambda_set)) + Ok(Layout::LambdaSet(lambda_set)) + } } Record(fields, ext_var) => { // extract any values from the ext_var diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index 53c069a190..e2b7013f75 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -713,12 +713,13 @@ fn solve( new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value); } - stack.push(Work::CheckForInfiniteTypes(local_def_vars)); stack.push(Work::Constraint { env: arena.alloc(new_env), rank, constraint: ret_constraint, }); + // Check for infinite types first + stack.push(Work::CheckForInfiniteTypes(local_def_vars)); continue; } @@ -831,12 +832,13 @@ fn solve( // Now solve the body, using the new vars_by_symbol which includes // the assignments' name-to-variable mappings. - stack.push(Work::CheckForInfiniteTypes(local_def_vars)); stack.push(Work::Constraint { env: arena.alloc(new_env), rank, constraint: ret_constraint, }); + // Check for infinite types first + stack.push(Work::CheckForInfiniteTypes(local_def_vars)); state = state_for_ret_con; @@ -2874,28 +2876,34 @@ fn check_for_infinite_type( ) { let var = loc_var.value; - while let Err((recursive, _chain)) = subs.occurs(var) { - // try to make a union recursive, see if that helps - match subs.get_content_without_compacting(recursive) { - &Content::Structure(FlatType::TagUnion(tags, ext_var)) => { - subs.mark_tag_union_recursive(recursive, tags, ext_var); - } - &Content::LambdaSet(subs::LambdaSet { - solved, - recursion_var: _, - unspecialized, - ambient_function: ambient_function_var, - }) => { - subs.mark_lambda_set_recursive( - recursive, + 'next_occurs_check: while let Err((_, chain)) = subs.occurs(var) { + // walk the chain till we find a tag union or lambda set, starting from the variable that + // occurred recursively, which is always at the end of the chain. + for &var in chain.iter().rev() { + match *subs.get_content_without_compacting(var) { + Content::Structure(FlatType::TagUnion(tags, ext_var)) => { + subs.mark_tag_union_recursive(var, tags, ext_var); + continue 'next_occurs_check; + } + Content::LambdaSet(subs::LambdaSet { solved, + recursion_var: _, unspecialized, - ambient_function_var, - ); + ambient_function: ambient_function_var, + }) => { + subs.mark_lambda_set_recursive( + var, + solved, + unspecialized, + ambient_function_var, + ); + continue 'next_occurs_check; + } + _ => { /* fall through */ } } - - _other => circular_error(subs, problems, symbol, &loc_var), } + + circular_error(subs, problems, symbol, &loc_var); } } diff --git a/crates/compiler/solve/tests/solve_expr.rs b/crates/compiler/solve/tests/solve_expr.rs index 6bacc74603..437a9090ea 100644 --- a/crates/compiler/solve/tests/solve_expr.rs +++ b/crates/compiler/solve/tests/solve_expr.rs @@ -7657,4 +7657,39 @@ mod solve_expr { "Num *", ); } + + #[test] + fn issue_3444() { + infer_queries!( + indoc!( + r#" + compose = \f, g -> + closCompose = \x -> g (f x) + closCompose + + const = \x -> + closConst = \_ -> x + closConst + + list = [] + + res : Str -> Str + res = List.walk list (const "z") (\c1, c2 -> compose c1 c2) + # ^^^^^ ^^^^^^^ + # ^^^^^^^^^^^^^^^^^^^^^^^^ + #^^^{-1} + + res "hello" + #^^^{-1} + "# + ), + @r###" + const : Str -[[const(2)]]-> (Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str) + compose : (Str -a-> Str), (Str -[[]]-> Str) -[[compose(1)]]-> (Str -a-> Str) + \c1, c2 -> compose c1 c2 : (Str -a-> Str), (Str -[[]]-> Str) -[[11(11)]]-> (Str -a-> Str) + res : Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str + res : Str -[[closCompose(7) (Str -a-> Str) (Str -[[]]-> Str), closConst(10) Str] as a]-> Str + "### + ); + } } diff --git a/crates/compiler/test_gen/src/gen_primitives.rs b/crates/compiler/test_gen/src/gen_primitives.rs index 52efe9b621..14376cbf9a 100644 --- a/crates/compiler/test_gen/src/gen_primitives.rs +++ b/crates/compiler/test_gen/src/gen_primitives.rs @@ -3705,3 +3705,136 @@ fn runtime_error_when_degenerate_pattern_reached() { true // allow errors ); } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn recursive_lambda_set_issue_3444() { + assert_evals_to!( + indoc!( + r#" + combine = \f, g -> \x -> g (f x) + const = \x -> (\_y -> x) + + list = [const "a", const "b", const "c"] + + res : Str -> Str + res = List.walk list (const "z") (\c1, c2 -> combine c1 c2) + res "hello" + "# + ), + RocStr::from("c"), + RocStr + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn recursive_lambda_set_toplevel_issue_3444() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + combine = \f, g -> \x -> g (f x) + const = \x -> (\_y -> x) + + list = [const "a", const "b", const "c"] + + res : Str -> Str + res = List.walk list (const "z") (\c1, c2 -> combine c1 c2) + + main = res "hello" + "# + ), + RocStr::from("c"), + RocStr + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn recursive_lambda_set_issue_3444_inferred() { + assert_evals_to!( + indoc!( + r#" + combine = \f, g -> \x -> g (f x) + const = \x -> (\_y -> x) + + list = [const "a", const "b", const "c"] + + res = List.walk list (const "z") (\c1, c2 -> combine c1 c2) + res "hello" + "# + ), + RocStr::from("c"), + RocStr + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn compose_recursive_lambda_set_productive_toplevel() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [main] to "./platform" + + compose = \f, g -> \x -> g (f x) + + identity = \x -> x + exclaim = \s -> "\(s)!" + whisper = \s -> "(\(s))" + + main = + res: Str -> Str + res = List.walk [ exclaim, whisper ] identity compose + res "hello" + "# + ), + RocStr::from("(hello!)"), + RocStr + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn compose_recursive_lambda_set_productive_nested() { + assert_evals_to!( + indoc!( + r#" + compose = \f, g -> \x -> g (f x) + + identity = \x -> x + exclaim = \s -> "\(s)!" + whisper = \s -> "(\(s))" + + res: Str -> Str + res = List.walk [ exclaim, whisper ] identity compose + res "hello" + "# + ), + RocStr::from("(hello!)"), + RocStr + ); +} + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn compose_recursive_lambda_set_productive_inferred() { + assert_evals_to!( + indoc!( + r#" + compose = \f, g -> \x -> g (f x) + + identity = \x -> x + exclaim = \s -> "\(s)!" + whisper = \s -> "(\(s))" + + res = List.walk [ exclaim, whisper ] identity compose + res "hello" + "# + ), + RocStr::from("(hello!)"), + RocStr + ); +} diff --git a/crates/compiler/unify/src/unify.rs b/crates/compiler/unify/src/unify.rs index 556d6ea297..e15185c27d 100644 --- a/crates/compiler/unify/src/unify.rs +++ b/crates/compiler/unify/src/unify.rs @@ -2518,10 +2518,12 @@ fn unify_flat_type( outcome.union(arg_outcome); if outcome.mismatches.is_empty() { + let merged_closure_var = choose_merged_var(env.subs, *l_closure, *r_closure); + outcome.union(merge( env, ctx, - Structure(Func(*r_args, *r_closure, *r_ret)), + Structure(Func(*r_args, merged_closure_var, *r_ret)), )); } diff --git a/crates/repl_eval/src/eval.rs b/crates/repl_eval/src/eval.rs index 993d18edc5..2a665face9 100644 --- a/crates/repl_eval/src/eval.rs +++ b/crates/repl_eval/src/eval.rs @@ -503,8 +503,9 @@ fn addr_to_ast<'a, M: ReplAppMemory>( unroll_newtypes_and_aliases(env, content); let expr = match (raw_content, layout) { - (Content::Structure(FlatType::Func(_, _, _)), _) - | (_, Layout::LambdaSet(_)) => OPAQUE_FUNCTION, + (Content::Structure(FlatType::Func(_, _, _)), _) | (_, Layout::LambdaSet(_)) => { + OPAQUE_FUNCTION + } (_, Layout::Builtin(Builtin::Bool)) => { // TODO: bits are not as expected here. // num is always false at the moment. @@ -549,7 +550,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( let arena_str = env.arena.alloc_str(string); Expr::Str(StrLiteral::PlainLine(arena_str)) } - (_, Layout::Struct{field_layouts, ..}) => match raw_content { + (_, Layout::Struct { field_layouts, .. }) => match raw_content { Content::Structure(FlatType::Record(fields, _)) => { struct_to_ast(env, mem, addr, *fields) } @@ -573,18 +574,37 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ); } }, - (_, Layout::RecursivePointer) => { - match (raw_content, when_recursive) { - (Content::RecursionVar { + (_, Layout::RecursivePointer) => match (raw_content, when_recursive) { + ( + Content::RecursionVar { structure, opt_name: _, - }, WhenRecursive::Loop(union_layout)) => { - let content = env.subs.get_content_without_compacting(*structure); - addr_to_ast(env, mem, addr, &union_layout, when_recursive, content) - } - other => unreachable!("Something had a RecursivePointer layout, but instead of being a RecursionVar and having a known recursive layout, I found {:?}", other), + }, + WhenRecursive::Loop(union_layout), + ) => { + let content = env.subs.get_content_without_compacting(*structure); + addr_to_ast(env, mem, addr, &union_layout, when_recursive, content) } - } + + ( + Content::RecursionVar { + structure, + opt_name: _, + }, + WhenRecursive::Unreachable, + ) => { + // It's possible to hit a recursive pointer before the full type layout; just + // figure out the actual recursive structure layout at this point. + let content = env.subs.get_content_without_compacting(*structure); + let union_layout = LayoutCache::new(env.target_info) + .from_var(env.arena, *structure, env.subs) + .expect("no layout for structure"); + debug_assert!(matches!(union_layout, Layout::Union(..))); + let when_recursive = WhenRecursive::Loop(union_layout); + addr_to_ast(env, mem, addr, &union_layout, when_recursive, content) + } + other => unreachable!("Something had a RecursivePointer layout, but instead of being a RecursionVar and having a known recursive layout, I found {:?}", other), + }, (_, Layout::Union(UnionLayout::NonRecursive(union_layouts))) => { let union_layout = UnionLayout::NonRecursive(union_layouts); @@ -608,8 +628,7 @@ fn addr_to_ast<'a, M: ReplAppMemory>( let tag_id = tag_id_from_data(env, mem, union_layout, addr); // use the tag ID as an index, to get its name and layout of any arguments - let (tag_name, arg_layouts) = - &tags_and_layouts[tag_id as usize]; + let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; expr_of_tag( env, @@ -623,18 +642,19 @@ fn addr_to_ast<'a, M: ReplAppMemory>( } (_, Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts))) => { let (rec_var, tags) = match raw_content { - Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => { + (rec_var, tags) + } _ => unreachable!("any other content would have a different layout"), }; debug_assert_eq!(union_layouts.len(), tags.len()); - let (vars_of_tag, union_variant) = - get_tags_vars_and_variant(env, tags, Some(*rec_var)); + let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, Some(*rec_var)); let tags_and_layouts = match union_variant { - UnionVariant::Wrapped(WrappedVariant::Recursive { + UnionVariant::Wrapped(WrappedVariant::Recursive { sorted_tag_layouts }) => { sorted_tag_layouts - }) => sorted_tag_layouts, + } _ => unreachable!("any other variant would have a different layout"), }; @@ -653,7 +673,9 @@ fn addr_to_ast<'a, M: ReplAppMemory>( } (_, Layout::Union(UnionLayout::NonNullableUnwrapped(_))) => { let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { - Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => { + (rec_var, tags) + } other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; debug_assert_eq!(tags.len(), 1); @@ -662,7 +684,8 @@ fn addr_to_ast<'a, M: ReplAppMemory>( let (tag_name, arg_layouts) = match union_variant { UnionVariant::Wrapped(WrappedVariant::NonNullableUnwrapped { - tag_name, fields, + tag_name, + fields, }) => (tag_name.expect_tag(), fields), _ => unreachable!("any other variant would have a different layout"), }; @@ -681,7 +704,9 @@ fn addr_to_ast<'a, M: ReplAppMemory>( } (_, Layout::Union(UnionLayout::NullableUnwrapped { .. })) => { let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { - Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => { + (rec_var, tags) + } other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; debug_assert!(tags.len() <= 2); @@ -694,7 +719,11 @@ fn addr_to_ast<'a, M: ReplAppMemory>( nullable_name, other_name, other_fields, - }) => (nullable_name.expect_tag(), other_name.expect_tag(), other_fields), + }) => ( + nullable_name.expect_tag(), + other_name.expect_tag(), + other_fields, + ), _ => unreachable!("any other variant would have a different layout"), }; @@ -715,7 +744,9 @@ fn addr_to_ast<'a, M: ReplAppMemory>( } (_, Layout::Union(union_layout @ UnionLayout::NullableWrapped { .. })) => { let (rec_var, tags) = match unroll_recursion_var(env, raw_content) { - Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => (rec_var, tags), + Content::Structure(FlatType::RecursiveTagUnion(rec_var, tags, _)) => { + (rec_var, tags) + } other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other), }; @@ -736,7 +767,11 @@ fn addr_to_ast<'a, M: ReplAppMemory>( } else { let (tag_id, data_addr) = tag_id_from_recursive_ptr(env, mem, *union_layout, addr); - let tag_id = if tag_id > nullable_id.into() { tag_id - 1 } else { tag_id }; + let tag_id = if tag_id > nullable_id.into() { + tag_id - 1 + } else { + tag_id + }; let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize]; expr_of_tag( @@ -750,7 +785,10 @@ fn addr_to_ast<'a, M: ReplAppMemory>( ) } } - (Content::Structure(FlatType::Apply(Symbol::BOX_BOX_TYPE, args)), Layout::Boxed(inner_layout)) => { + ( + Content::Structure(FlatType::Apply(Symbol::BOX_BOX_TYPE, args)), + Layout::Boxed(inner_layout), + ) => { debug_assert_eq!(args.len(), 1); let inner_var_index = args.into_iter().next().unwrap(); @@ -758,17 +796,27 @@ fn addr_to_ast<'a, M: ReplAppMemory>( let inner_content = env.subs.get_content_without_compacting(inner_var); let addr_of_inner = mem.deref_usize(addr); - let inner_expr = addr_to_ast(env, mem, addr_of_inner, inner_layout, WhenRecursive::Unreachable, inner_content); + let inner_expr = addr_to_ast( + env, + mem, + addr_of_inner, + inner_layout, + WhenRecursive::Unreachable, + inner_content, + ); let box_box = env.arena.alloc(Loc::at_zero(Expr::Var { - module_name: "Box", ident: "box" + module_name: "Box", + ident: "box", })); let box_box_arg = &*env.arena.alloc(Loc::at_zero(inner_expr)); let box_box_args = env.arena.alloc([box_box_arg]); Expr::Apply(box_box, box_box_args, CalledVia::Space) } - (_, Layout::Boxed(_)) => unreachable!("Box layouts can only be behind a `Box.Box` application"), + (_, Layout::Boxed(_)) => { + unreachable!("Box layouts can only be behind a `Box.Box` application") + } other => { todo!( "TODO add support for rendering pointer to {:?} in the REPL", diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index 83de08443b..70385805a3 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -1004,16 +1004,16 @@ mod test_reporting { @r###" ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `g`: + I'm inferring a weird self-referential type for `f`: 4│ f = \g -> g g - ^ + ^ Here is my best effort at writing down the type. You will see ∞ for parts of the type that repeat something already printed out infinitely. - ∞ -> a + (∞ -> a) -> a "### ); @@ -1188,20 +1188,20 @@ mod test_reporting { f "# ), - @r#" + @r###" ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `x`: + I'm inferring a weird self-referential type for `f`: 5│ f = \x -> f [x] - ^ + ^ Here is my best effort at writing down the type. You will see ∞ for parts of the type that repeat something already printed out infinitely. - List ∞ - "# + List ∞ -> List a + "### ); test_report!( @@ -1247,32 +1247,6 @@ mod test_reporting { 6│ g = \x -> f [x] ^ - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. - - List ∞ -> List a - - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - - I'm inferring a weird self-referential type for `f`: - - 5│ f = \x -> g x - ^ - - Here is my best effort at writing down the type. You will see ∞ for - parts of the type that repeat something already printed out - infinitely. - - List ∞ -> List a - - ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - - I'm inferring a weird self-referential type for `main`: - - 3│ main = - ^^^^ - Here is my best effort at writing down the type. You will see ∞ for parts of the type that repeat something already printed out infinitely. @@ -1292,20 +1266,33 @@ mod test_reporting { f "# ), - @r#" + @r###" ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ - I'm inferring a weird self-referential type for `x`: + I'm inferring a weird self-referential type for `f`: - 6│ g = \x -> f [x] - ^ + 4│ f = \x -> g x + ^ Here is my best effort at writing down the type. You will see ∞ for parts of the type that repeat something already printed out infinitely. - List ∞ - "# + List ∞ -> List a + + ── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─ + + I'm inferring a weird self-referential type for `g`: + + 6│ g = \x -> f [x] + ^ + + Here is my best effort at writing down the type. You will see ∞ for + parts of the type that repeat something already printed out + infinitely. + + List ∞ -> List a + "### ); test_report!(