mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-27 13:59:08 +00:00
Merge pull request #3738 from rtfeldman/i3444
Layout generation for recursive lambda sets
This commit is contained in:
commit
ae0e90c8f3
8 changed files with 441 additions and 110 deletions
|
@ -3130,7 +3130,12 @@ fn specialize_external<'a>(
|
||||||
tag_id,
|
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());
|
debug_assert_eq!(field_layouts.len(), captured.len());
|
||||||
|
|
||||||
// captured variables are in symbol-alphabetic order, but now we want
|
// captured variables are in symbol-alphabetic order, but now we want
|
||||||
|
@ -3149,7 +3154,9 @@ fn specialize_external<'a>(
|
||||||
size2.cmp(&size1)
|
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 {
|
let expr = Expr::UnionAtIndex {
|
||||||
tag_id,
|
tag_id,
|
||||||
structure: Symbol::ARG_CLOSURE,
|
structure: Symbol::ARG_CLOSURE,
|
||||||
|
@ -3162,7 +3169,7 @@ fn specialize_external<'a>(
|
||||||
specialized_body = Stmt::Let(
|
specialized_body = Stmt::Let(
|
||||||
symbol,
|
symbol,
|
||||||
expr,
|
expr,
|
||||||
**layout,
|
layout,
|
||||||
env.arena.alloc(specialized_body),
|
env.arena.alloc(specialized_body),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,8 @@ use roc_problem::can::RuntimeError;
|
||||||
use roc_target::{PtrWidth, TargetInfo};
|
use roc_target::{PtrWidth, TargetInfo};
|
||||||
use roc_types::num::NumericRange;
|
use roc_types::num::NumericRange;
|
||||||
use roc_types::subs::{
|
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 roc_types::types::{gather_fields_unsorted_iter, RecordField, RecordFieldsError};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
@ -842,6 +843,7 @@ impl<'a> LambdaSet<'a> {
|
||||||
|
|
||||||
let comparator = |other_name: Symbol, other_captures_layouts: &[Layout]| {
|
let comparator = |other_name: Symbol, other_captures_layouts: &[Layout]| {
|
||||||
other_name == lambda_name.name
|
other_name == lambda_name.name
|
||||||
|
// Make sure all captures are equal
|
||||||
&& other_captures_layouts
|
&& other_captures_layouts
|
||||||
.iter()
|
.iter()
|
||||||
.eq(lambda_name.captures_niche.0)
|
.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");
|
debug_assert!(self.contains(function_symbol), "function symbol not in set");
|
||||||
|
|
||||||
let comparator = |other_name: Symbol, other_captures_layouts: &[Layout]| {
|
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
|
let (name, layouts) = self
|
||||||
.set
|
.set
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(name, layouts)| comparator(*name, layouts))
|
.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 {
|
LambdaName {
|
||||||
name: *name,
|
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<F>(&self, comparator: F) -> ClosureRepresentation<'a>
|
fn layout_for_member<F>(&self, comparator: F) -> ClosureRepresentation<'a>
|
||||||
where
|
where
|
||||||
F: Fn(Symbol, &[Layout]) -> bool,
|
F: Fn(Symbol, &[Layout]) -> bool,
|
||||||
|
@ -902,16 +947,48 @@ impl<'a> LambdaSet<'a> {
|
||||||
union_layout: *union,
|
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::NonNullableUnwrapped(_) => todo!("recursive closures"),
|
||||||
UnionLayout::NullableWrapped {
|
UnionLayout::NullableWrapped {
|
||||||
nullable_id: _,
|
nullable_id: _,
|
||||||
other_tags: _,
|
other_tags: _,
|
||||||
} => todo!("recursive closures"),
|
} => todo!("recursive closures"),
|
||||||
UnionLayout::NullableUnwrapped {
|
|
||||||
nullable_id: _,
|
|
||||||
other_fields: _,
|
|
||||||
} => todo!("recursive closures"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Layout::Struct { .. } => {
|
Layout::Struct { .. } => {
|
||||||
|
@ -971,7 +1048,7 @@ impl<'a> LambdaSet<'a> {
|
||||||
target_info: TargetInfo,
|
target_info: TargetInfo,
|
||||||
) -> Result<Self, LayoutProblem> {
|
) -> Result<Self, LayoutProblem> {
|
||||||
match resolve_lambda_set(subs, closure_var) {
|
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!
|
// sort the tags; make sure ordering stays intact!
|
||||||
lambdas.sort_by_key(|(sym, _)| *sym);
|
lambdas.sort_by_key(|(sym, _)| *sym);
|
||||||
|
|
||||||
|
@ -992,6 +1069,9 @@ impl<'a> LambdaSet<'a> {
|
||||||
seen: Vec::new_in(arena),
|
seen: Vec::new_in(arena),
|
||||||
target_info,
|
target_info,
|
||||||
};
|
};
|
||||||
|
if let Some(rec_var) = opt_recursion_var.into_variable() {
|
||||||
|
env.insert_seen(rec_var);
|
||||||
|
}
|
||||||
|
|
||||||
for var in variables {
|
for var in variables {
|
||||||
arguments.push(Layout::from_var(&mut env, *var)?);
|
arguments.push(Layout::from_var(&mut env, *var)?);
|
||||||
|
@ -1048,6 +1128,7 @@ impl<'a> LambdaSet<'a> {
|
||||||
arena,
|
arena,
|
||||||
subs,
|
subs,
|
||||||
set_with_variables,
|
set_with_variables,
|
||||||
|
opt_recursion_var.into_variable(),
|
||||||
target_info,
|
target_info,
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -1071,10 +1152,28 @@ impl<'a> LambdaSet<'a> {
|
||||||
arena: &'a Bump,
|
arena: &'a Bump,
|
||||||
subs: &Subs,
|
subs: &Subs,
|
||||||
tags: std::vec::Vec<(Symbol, std::vec::Vec<Variable>)>,
|
tags: std::vec::Vec<(Symbol, std::vec::Vec<Variable>)>,
|
||||||
|
opt_rec_var: Option<Variable>,
|
||||||
target_info: TargetInfo,
|
target_info: TargetInfo,
|
||||||
) -> Layout<'a> {
|
) -> 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
|
// 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::*;
|
use UnionVariant::*;
|
||||||
match variant {
|
match variant {
|
||||||
|
@ -1108,7 +1207,12 @@ impl<'a> LambdaSet<'a> {
|
||||||
Layout::Union(UnionLayout::NonRecursive(tag_arguments.into_bump_slice()))
|
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 {
|
enum ResolvedLambdaSet {
|
||||||
Set(std::vec::Vec<(Symbol, std::vec::Vec<Variable>)>),
|
Set(
|
||||||
|
std::vec::Vec<(Symbol, std::vec::Vec<Variable>)>,
|
||||||
|
OptVariable,
|
||||||
|
),
|
||||||
/// TODO: figure out if this can happen in a correct program, or is the result of a bug in our
|
/// 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.
|
/// compiler. See https://github.com/rtfeldman/roc/issues/3163.
|
||||||
Unbound,
|
Unbound,
|
||||||
|
@ -1142,7 +1249,7 @@ fn resolve_lambda_set(subs: &Subs, mut var: Variable) -> ResolvedLambdaSet {
|
||||||
match subs.get_content_without_compacting(var) {
|
match subs.get_content_without_compacting(var) {
|
||||||
Content::LambdaSet(subs::LambdaSet {
|
Content::LambdaSet(subs::LambdaSet {
|
||||||
solved,
|
solved,
|
||||||
recursion_var: _,
|
recursion_var,
|
||||||
unspecialized,
|
unspecialized,
|
||||||
ambient_function: _,
|
ambient_function: _,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -1153,7 +1260,7 @@ fn resolve_lambda_set(subs: &Subs, mut var: Variable) -> ResolvedLambdaSet {
|
||||||
subs.uls_of_var
|
subs.uls_of_var
|
||||||
);
|
);
|
||||||
roc_types::pretty_print::push_union(subs, solved, &mut set);
|
roc_types::pretty_print::push_union(subs, solved, &mut set);
|
||||||
return ResolvedLambdaSet::Set(set);
|
return ResolvedLambdaSet::Set(set, *recursion_var);
|
||||||
}
|
}
|
||||||
Content::RecursionVar { structure, .. } => {
|
Content::RecursionVar { structure, .. } => {
|
||||||
var = *structure;
|
var = *structure;
|
||||||
|
@ -2130,10 +2237,14 @@ fn layout_from_flat_type<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Func(_, closure_var, _) => {
|
Func(_, closure_var, _) => {
|
||||||
let lambda_set =
|
if env.is_seen(closure_var) {
|
||||||
LambdaSet::from_var(env.arena, env.subs, closure_var, env.target_info)?;
|
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) => {
|
Record(fields, ext_var) => {
|
||||||
// extract any values from the ext_var
|
// extract any values from the ext_var
|
||||||
|
|
|
@ -713,12 +713,13 @@ fn solve(
|
||||||
new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value);
|
new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
stack.push(Work::CheckForInfiniteTypes(local_def_vars));
|
|
||||||
stack.push(Work::Constraint {
|
stack.push(Work::Constraint {
|
||||||
env: arena.alloc(new_env),
|
env: arena.alloc(new_env),
|
||||||
rank,
|
rank,
|
||||||
constraint: ret_constraint,
|
constraint: ret_constraint,
|
||||||
});
|
});
|
||||||
|
// Check for infinite types first
|
||||||
|
stack.push(Work::CheckForInfiniteTypes(local_def_vars));
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -831,12 +832,13 @@ fn solve(
|
||||||
|
|
||||||
// Now solve the body, using the new vars_by_symbol which includes
|
// Now solve the body, using the new vars_by_symbol which includes
|
||||||
// the assignments' name-to-variable mappings.
|
// the assignments' name-to-variable mappings.
|
||||||
stack.push(Work::CheckForInfiniteTypes(local_def_vars));
|
|
||||||
stack.push(Work::Constraint {
|
stack.push(Work::Constraint {
|
||||||
env: arena.alloc(new_env),
|
env: arena.alloc(new_env),
|
||||||
rank,
|
rank,
|
||||||
constraint: ret_constraint,
|
constraint: ret_constraint,
|
||||||
});
|
});
|
||||||
|
// Check for infinite types first
|
||||||
|
stack.push(Work::CheckForInfiniteTypes(local_def_vars));
|
||||||
|
|
||||||
state = state_for_ret_con;
|
state = state_for_ret_con;
|
||||||
|
|
||||||
|
@ -2874,28 +2876,34 @@ fn check_for_infinite_type(
|
||||||
) {
|
) {
|
||||||
let var = loc_var.value;
|
let var = loc_var.value;
|
||||||
|
|
||||||
while let Err((recursive, _chain)) = subs.occurs(var) {
|
'next_occurs_check: while let Err((_, chain)) = subs.occurs(var) {
|
||||||
// try to make a union recursive, see if that helps
|
// walk the chain till we find a tag union or lambda set, starting from the variable that
|
||||||
match subs.get_content_without_compacting(recursive) {
|
// occurred recursively, which is always at the end of the chain.
|
||||||
&Content::Structure(FlatType::TagUnion(tags, ext_var)) => {
|
for &var in chain.iter().rev() {
|
||||||
subs.mark_tag_union_recursive(recursive, tags, ext_var);
|
match *subs.get_content_without_compacting(var) {
|
||||||
}
|
Content::Structure(FlatType::TagUnion(tags, ext_var)) => {
|
||||||
&Content::LambdaSet(subs::LambdaSet {
|
subs.mark_tag_union_recursive(var, tags, ext_var);
|
||||||
solved,
|
continue 'next_occurs_check;
|
||||||
recursion_var: _,
|
}
|
||||||
unspecialized,
|
Content::LambdaSet(subs::LambdaSet {
|
||||||
ambient_function: ambient_function_var,
|
|
||||||
}) => {
|
|
||||||
subs.mark_lambda_set_recursive(
|
|
||||||
recursive,
|
|
||||||
solved,
|
solved,
|
||||||
|
recursion_var: _,
|
||||||
unspecialized,
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7657,4 +7657,39 @@ mod solve_expr {
|
||||||
"Num *",
|
"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
|
||||||
|
"###
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3705,3 +3705,136 @@ fn runtime_error_when_degenerate_pattern_reached() {
|
||||||
true // allow errors
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -2518,10 +2518,12 @@ fn unify_flat_type<M: MetaCollector>(
|
||||||
outcome.union(arg_outcome);
|
outcome.union(arg_outcome);
|
||||||
|
|
||||||
if outcome.mismatches.is_empty() {
|
if outcome.mismatches.is_empty() {
|
||||||
|
let merged_closure_var = choose_merged_var(env.subs, *l_closure, *r_closure);
|
||||||
|
|
||||||
outcome.union(merge(
|
outcome.union(merge(
|
||||||
env,
|
env,
|
||||||
ctx,
|
ctx,
|
||||||
Structure(Func(*r_args, *r_closure, *r_ret)),
|
Structure(Func(*r_args, merged_closure_var, *r_ret)),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -503,8 +503,9 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||||
unroll_newtypes_and_aliases(env, content);
|
unroll_newtypes_and_aliases(env, content);
|
||||||
|
|
||||||
let expr = match (raw_content, layout) {
|
let expr = match (raw_content, layout) {
|
||||||
(Content::Structure(FlatType::Func(_, _, _)), _)
|
(Content::Structure(FlatType::Func(_, _, _)), _) | (_, Layout::LambdaSet(_)) => {
|
||||||
| (_, Layout::LambdaSet(_)) => OPAQUE_FUNCTION,
|
OPAQUE_FUNCTION
|
||||||
|
}
|
||||||
(_, Layout::Builtin(Builtin::Bool)) => {
|
(_, Layout::Builtin(Builtin::Bool)) => {
|
||||||
// TODO: bits are not as expected here.
|
// TODO: bits are not as expected here.
|
||||||
// num is always false at the moment.
|
// 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);
|
let arena_str = env.arena.alloc_str(string);
|
||||||
Expr::Str(StrLiteral::PlainLine(arena_str))
|
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, _)) => {
|
Content::Structure(FlatType::Record(fields, _)) => {
|
||||||
struct_to_ast(env, mem, addr, *fields)
|
struct_to_ast(env, mem, addr, *fields)
|
||||||
}
|
}
|
||||||
|
@ -573,18 +574,37 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(_, Layout::RecursivePointer) => {
|
(_, Layout::RecursivePointer) => match (raw_content, when_recursive) {
|
||||||
match (raw_content, when_recursive) {
|
(
|
||||||
(Content::RecursionVar {
|
Content::RecursionVar {
|
||||||
structure,
|
structure,
|
||||||
opt_name: _,
|
opt_name: _,
|
||||||
}, WhenRecursive::Loop(union_layout)) => {
|
},
|
||||||
let content = env.subs.get_content_without_compacting(*structure);
|
WhenRecursive::Loop(union_layout),
|
||||||
addr_to_ast(env, mem, addr, &union_layout, when_recursive, content)
|
) => {
|
||||||
}
|
let content = env.subs.get_content_without_compacting(*structure);
|
||||||
other => unreachable!("Something had a RecursivePointer layout, but instead of being a RecursionVar and having a known recursive layout, I found {:?}", other),
|
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))) => {
|
(_, Layout::Union(UnionLayout::NonRecursive(union_layouts))) => {
|
||||||
let union_layout = 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);
|
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
|
// use the tag ID as an index, to get its name and layout of any arguments
|
||||||
let (tag_name, arg_layouts) =
|
let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize];
|
||||||
&tags_and_layouts[tag_id as usize];
|
|
||||||
|
|
||||||
expr_of_tag(
|
expr_of_tag(
|
||||||
env,
|
env,
|
||||||
|
@ -623,18 +642,19 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||||
}
|
}
|
||||||
(_, Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts))) => {
|
(_, Layout::Union(union_layout @ UnionLayout::Recursive(union_layouts))) => {
|
||||||
let (rec_var, tags) = match raw_content {
|
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"),
|
_ => unreachable!("any other content would have a different layout"),
|
||||||
};
|
};
|
||||||
debug_assert_eq!(union_layouts.len(), tags.len());
|
debug_assert_eq!(union_layouts.len(), tags.len());
|
||||||
|
|
||||||
let (vars_of_tag, union_variant) =
|
let (vars_of_tag, union_variant) = get_tags_vars_and_variant(env, tags, Some(*rec_var));
|
||||||
get_tags_vars_and_variant(env, tags, Some(*rec_var));
|
|
||||||
|
|
||||||
let tags_and_layouts = match union_variant {
|
let tags_and_layouts = match union_variant {
|
||||||
UnionVariant::Wrapped(WrappedVariant::Recursive {
|
UnionVariant::Wrapped(WrappedVariant::Recursive { sorted_tag_layouts }) => {
|
||||||
sorted_tag_layouts
|
sorted_tag_layouts
|
||||||
}) => sorted_tag_layouts,
|
}
|
||||||
_ => unreachable!("any other variant would have a different layout"),
|
_ => unreachable!("any other variant would have a different layout"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -653,7 +673,9 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||||
}
|
}
|
||||||
(_, Layout::Union(UnionLayout::NonNullableUnwrapped(_))) => {
|
(_, Layout::Union(UnionLayout::NonNullableUnwrapped(_))) => {
|
||||||
let (rec_var, tags) = match unroll_recursion_var(env, raw_content) {
|
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),
|
other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other),
|
||||||
};
|
};
|
||||||
debug_assert_eq!(tags.len(), 1);
|
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 {
|
let (tag_name, arg_layouts) = match union_variant {
|
||||||
UnionVariant::Wrapped(WrappedVariant::NonNullableUnwrapped {
|
UnionVariant::Wrapped(WrappedVariant::NonNullableUnwrapped {
|
||||||
tag_name, fields,
|
tag_name,
|
||||||
|
fields,
|
||||||
}) => (tag_name.expect_tag(), fields),
|
}) => (tag_name.expect_tag(), fields),
|
||||||
_ => unreachable!("any other variant would have a different layout"),
|
_ => unreachable!("any other variant would have a different layout"),
|
||||||
};
|
};
|
||||||
|
@ -681,7 +704,9 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||||
}
|
}
|
||||||
(_, Layout::Union(UnionLayout::NullableUnwrapped { .. })) => {
|
(_, Layout::Union(UnionLayout::NullableUnwrapped { .. })) => {
|
||||||
let (rec_var, tags) = match unroll_recursion_var(env, raw_content) {
|
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),
|
other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other),
|
||||||
};
|
};
|
||||||
debug_assert!(tags.len() <= 2);
|
debug_assert!(tags.len() <= 2);
|
||||||
|
@ -694,7 +719,11 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||||
nullable_name,
|
nullable_name,
|
||||||
other_name,
|
other_name,
|
||||||
other_fields,
|
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"),
|
_ => 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 { .. })) => {
|
(_, Layout::Union(union_layout @ UnionLayout::NullableWrapped { .. })) => {
|
||||||
let (rec_var, tags) = match unroll_recursion_var(env, raw_content) {
|
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),
|
other => unreachable!("Unexpected content for NonNullableUnwrapped: {:?}", other),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -736,7 +767,11 @@ fn addr_to_ast<'a, M: ReplAppMemory>(
|
||||||
} else {
|
} else {
|
||||||
let (tag_id, data_addr) = tag_id_from_recursive_ptr(env, mem, *union_layout, addr);
|
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];
|
let (tag_name, arg_layouts) = &tags_and_layouts[tag_id as usize];
|
||||||
expr_of_tag(
|
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);
|
debug_assert_eq!(args.len(), 1);
|
||||||
|
|
||||||
let inner_var_index = args.into_iter().next().unwrap();
|
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 inner_content = env.subs.get_content_without_compacting(inner_var);
|
||||||
|
|
||||||
let addr_of_inner = mem.deref_usize(addr);
|
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 {
|
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_arg = &*env.arena.alloc(Loc::at_zero(inner_expr));
|
||||||
let box_box_args = env.arena.alloc([box_box_arg]);
|
let box_box_args = env.arena.alloc([box_box_arg]);
|
||||||
|
|
||||||
Expr::Apply(box_box, box_box_args, CalledVia::Space)
|
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 => {
|
other => {
|
||||||
todo!(
|
todo!(
|
||||||
"TODO add support for rendering pointer to {:?} in the REPL",
|
"TODO add support for rendering pointer to {:?} in the REPL",
|
||||||
|
|
|
@ -1004,16 +1004,16 @@ mod test_reporting {
|
||||||
@r###"
|
@r###"
|
||||||
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
|
── 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
|
4│ f = \g -> g g
|
||||||
^
|
^
|
||||||
|
|
||||||
Here is my best effort at writing down the type. You will see ∞ for
|
Here is my best effort at writing down the type. You will see ∞ for
|
||||||
parts of the type that repeat something already printed out
|
parts of the type that repeat something already printed out
|
||||||
infinitely.
|
infinitely.
|
||||||
|
|
||||||
∞ -> a
|
(∞ -> a) -> a
|
||||||
"###
|
"###
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1188,20 +1188,20 @@ mod test_reporting {
|
||||||
f
|
f
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
@r#"
|
@r###"
|
||||||
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
|
── 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]
|
5│ f = \x -> f [x]
|
||||||
^
|
^
|
||||||
|
|
||||||
Here is my best effort at writing down the type. You will see ∞ for
|
Here is my best effort at writing down the type. You will see ∞ for
|
||||||
parts of the type that repeat something already printed out
|
parts of the type that repeat something already printed out
|
||||||
infinitely.
|
infinitely.
|
||||||
|
|
||||||
List ∞
|
List ∞ -> List a
|
||||||
"#
|
"###
|
||||||
);
|
);
|
||||||
|
|
||||||
test_report!(
|
test_report!(
|
||||||
|
@ -1247,32 +1247,6 @@ mod test_reporting {
|
||||||
6│ g = \x -> f [x]
|
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
|
Here is my best effort at writing down the type. You will see ∞ for
|
||||||
parts of the type that repeat something already printed out
|
parts of the type that repeat something already printed out
|
||||||
infinitely.
|
infinitely.
|
||||||
|
@ -1292,20 +1266,33 @@ mod test_reporting {
|
||||||
f
|
f
|
||||||
"#
|
"#
|
||||||
),
|
),
|
||||||
@r#"
|
@r###"
|
||||||
── CIRCULAR TYPE ───────────────────────────────────────── /code/proj/Main.roc ─
|
── 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
|
Here is my best effort at writing down the type. You will see ∞ for
|
||||||
parts of the type that repeat something already printed out
|
parts of the type that repeat something already printed out
|
||||||
infinitely.
|
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!(
|
test_report!(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue