Merge remote-tracking branch 'origin/trunk' into fix-nested-imports

This commit is contained in:
Richard Feldman 2022-07-10 08:52:38 -04:00
commit c45e3ec4b4
No known key found for this signature in database
GPG key ID: 7E4127D1E4241798
93 changed files with 3258 additions and 4416 deletions

View file

@ -23,6 +23,7 @@ ROC_WORKSPACE_DIR = { value = "", relative = true }
# Set = "1" to turn a debug flag on.
ROC_PRETTY_PRINT_ALIAS_CONTENTS = "0"
ROC_PRINT_UNIFICATIONS = "0"
ROC_TRACE_COMPACTION = "0"
ROC_PRINT_UNIFICATIONS_DERIVED = "0"
ROC_PRINT_MISMATCHES = "0"
ROC_VERIFY_RIGID_LET_GENERALIZED = "0"

3
.gitignore vendored
View file

@ -20,6 +20,9 @@ vgcore.*
.idea/
.vscode/
.ignore
.exrc
.vimrc
.nvimrc
#files too big to track in git
editor/benches/resources/100000_lines.roc

View file

@ -1421,6 +1421,7 @@ fn adjust_rank_content(
recursion_var,
// TODO: handle unspecialized
unspecialized: _,
ambient_function: _,
}) => {
let mut rank = group_rank;
@ -1623,6 +1624,7 @@ fn instantiate_rigids_help(
recursion_var,
// TODO: handle unspecialized
unspecialized: _,
ambient_function: _,
}) => {
if let Some(rec_var) = recursion_var.into_variable() {
instantiate_rigids_help(subs, max_rank, pools, rec_var);
@ -1903,6 +1905,7 @@ fn deep_copy_var_help(
solved,
recursion_var,
unspecialized,
ambient_function,
}) => {
let mut new_variable_slices = Vec::with_capacity(solved.len());
@ -1937,6 +1940,7 @@ fn deep_copy_var_help(
recursion_var: new_rec_var,
// TODO: actually copy
unspecialized,
ambient_function,
});
subs.set(copy, make_descriptor(new_content));

View file

@ -1075,7 +1075,7 @@ fn lowlevel_spec(
builder.add_make_tuple(block, &[cell, bag])
}
StrFromUtf8 => {
StrFromUtf8Range => {
let list = env.symbols[&arguments[0]];
let cell = builder.add_get_tuple_field(block, list, LIST_CELL_INDEX)?;

View file

@ -1638,18 +1638,45 @@ inline fn fromUtf8(arg: RocList, update_mode: UpdateMode) FromUtf8Result {
}
}
pub fn fromUtf8RangeC(output: *FromUtf8Result, arg: RocList, countAndStart: CountAndStart) callconv(.C) void {
output.* = @call(.{ .modifier = always_inline }, fromUtf8Range, .{ arg, countAndStart });
pub fn fromUtf8RangeC(
output: *FromUtf8Result,
list: RocList,
start: usize,
count: usize,
update_mode: UpdateMode,
) callconv(.C) void {
output.* = @call(.{ .modifier = always_inline }, fromUtf8Range, .{ list, start, count, update_mode });
}
fn fromUtf8Range(arg: RocList, countAndStart: CountAndStart) FromUtf8Result {
const bytes = @ptrCast([*]const u8, arg.bytes)[countAndStart.start..countAndStart.count];
pub fn fromUtf8Range(arg: RocList, start: usize, count: usize, update_mode: UpdateMode) FromUtf8Result {
const bytes = @ptrCast([*]const u8, arg.bytes)[start..count];
if (unicode.utf8ValidateSlice(bytes)) {
// the output will be correct. Now we need to clone the input
const string = RocStr.init(@ptrCast([*]const u8, bytes), countAndStart.count);
return FromUtf8Result{ .is_ok = true, .string = string, .byte_index = 0, .problem_code = Utf8ByteProblem.InvalidStartByte };
if (count == arg.len() and count > SMALL_STR_MAX_LENGTH) {
const byte_list = arg.makeUniqueExtra(RocStr.alignment, @sizeOf(u8), update_mode);
const string = RocStr{
.str_bytes = byte_list.bytes,
.str_len = byte_list.length,
.str_capacity = byte_list.capacity,
};
return FromUtf8Result{
.is_ok = true,
.string = string,
.byte_index = 0,
.problem_code = Utf8ByteProblem.InvalidStartByte,
};
} else {
return FromUtf8Result{
.is_ok = true,
.string = RocStr.init(@ptrCast([*]const u8, bytes), count),
.byte_index = 0,
.problem_code = Utf8ByteProblem.InvalidStartByte,
};
}
} else {
const temp = errorToProblem(@ptrCast([*]u8, arg.bytes), arg.length);
return FromUtf8Result{ .is_ok = false, .string = RocStr.empty(), .byte_index = temp.index, .problem_code = temp.problem };

View file

@ -68,14 +68,27 @@ interface Dict
## `fn dict1 == fn dict2` also being `True`, even if `fn` relies on the dictionary's ordering.
## An empty dictionary.
empty : Dict k v
single : k, v -> Dict k v
get : Dict k v, k -> Result v [KeyNotFound]*
get = \dict, key ->
result = getLowlevel dict key
when result.flag is
True -> Ok result.value
False -> Err KeyNotFound
getLowlevel : Dict k v, k -> { flag : Bool, value : v }
walk : Dict k v, state, (state, k, v -> state) -> state
insert : Dict k v, k, v -> Dict k v
len : Dict k v -> Nat
remove : Dict k v, k -> Dict k v
contains : Dict k v, k -> Bool
single : k, v -> Dict k v
single = \key, value ->
Dict.empty
|> Dict.insert key value
## Returns a [List] of the dictionary's keys.
keys : Dict k v -> List k

View file

@ -717,6 +717,10 @@ takeLast = \list, outputLength ->
## Drops n elements from the beginning of the list.
drop : List elem, Nat -> List elem
drop = \list, n ->
remaining = Num.subSaturated (List.len list) n
List.takeLast list remaining
## Drops the element at the given index from the list.
##
@ -812,6 +816,10 @@ findIndex = \list, matcher ->
##
## Some languages have a function called **`slice`** which works similarly to this.
sublist : List elem, { start : Nat, len : Nat } -> List elem
sublist = \list, config ->
sublistLowlevel list config.start config.len
sublistLowlevel : List elem, Nat, Nat -> List elem
## Intersperses `sep` between the elements of `list`
## >>> List.intersperse 9 [1, 2, 3] # [1, 9, 2, 9, 3]
@ -835,6 +843,13 @@ intersperse = \list, sep ->
## means if you give an index of 0, the `before` list will be empty and the
## `others` list will have the same elements as the original list.)
split : List elem, Nat -> { before : List elem, others : List elem }
split = \elements, userSplitIndex ->
length = List.len elements
splitIndex = if length > userSplitIndex then userSplitIndex else length
before = List.sublist elements { start: 0, len: splitIndex }
others = List.sublist elements { start: splitIndex, len: length - splitIndex }
{ before, others }
## Primitive for iterating over a List, being able to decide at every element whether to continue
iterate : List elem, s, (s, elem -> [Continue s, Break b]) -> [Continue s, Break b]

View file

@ -509,8 +509,28 @@ Dec : Num (FloatingPoint Decimal)
toStr : Num * -> Str
intCast : Int a -> Int b
bytesToU16Lowlevel : List U8, Nat -> U16
bytesToU32Lowlevel : List U8, Nat -> U32
bytesToU16 : List U8, Nat -> Result U16 [OutOfBounds]
bytesToU16 = \bytes, index ->
# we need at least 1 more byte
offset = 1
if index + offset < List.len bytes then
Ok (bytesToU16Lowlevel bytes index)
else
Err OutOfBounds
bytesToU32 : List U8, Nat -> Result U32 [OutOfBounds]
bytesToU32 = \bytes, index ->
# we need at least 3 more bytes
offset = 3
if index + offset < List.len bytes then
Ok (bytesToU32Lowlevel bytes index)
else
Err OutOfBounds
compare : Num a, Num a -> [LT, EQ, GT]
@ -554,22 +574,27 @@ isGte : Num a, Num a -> Bool
## Returns `True` if the number is `0`, and `False` otherwise.
isZero : Num a -> Bool
isZero = \x -> x == 0
## A number is even if dividing it by 2 gives a remainder of 0.
##
## Examples of even numbers: 0, 2, 4, 6, 8, -2, -4, -6, -8
isEven : Int a -> Bool
isEven = \x -> Num.isMultipleOf x 2
## A number is odd if dividing it by 2 gives a remainder of 1.
##
## Examples of odd numbers: 1, 3, 5, 7, -1, -3, -5, -7
isOdd : Int a -> Bool
isOdd = \x -> Bool.not (Num.isMultipleOf x 2)
## Positive numbers are greater than `0`.
isPositive : Num a -> Bool
isPositive = \x -> x > 0
## Negative numbers are less than `0`.
isNegative : Num a -> Bool
isNegative = \x -> x < 0
toFrac : Num * -> Frac *
@ -682,7 +707,11 @@ mul : Num a, Num a -> Num a
sin : Frac a -> Frac a
cos : Frac a -> Frac a
tan : Frac a -> Frac a
tan = \x ->
# `tan` is not available as an intrinsic in LLVM
Num.div (Num.sin x) (Num.cos x)
asin : Frac a -> Frac a
acos : Frac a -> Frac a
@ -713,9 +742,22 @@ atan : Frac a -> Frac a
##
## >>> Num.sqrt -4.0f64
sqrt : Frac a -> Frac a
sqrtChecked : Frac a -> Result (Frac a) [SqrtOfNegative]*
sqrtChecked = \x ->
if x < 0.0 then
Err SqrtOfNegative
else
Ok (Num.sqrt x)
log : Frac a -> Frac a
logChecked : Frac a -> Result (Frac a) [LogNeedsPositive]*
logChecked = \x ->
if x <= 0.0 then
Err LogNeedsPositive
else
Ok (Num.log x)
## Divide one [Frac] by another.
##
@ -748,9 +790,22 @@ logChecked : Frac a -> Result (Frac a) [LogNeedsPositive]*
## >>> Num.pi
## >>> |> Num.div 2.0
div : Frac a, Frac a -> Frac a
divChecked : Frac a, Frac a -> Result (Frac a) [DivByZero]*
divChecked = \a, b ->
if b == 0 then
Err DivByZero
else
Ok (Num.div a b)
divCeil : Int a, Int a -> Int a
divCeilChecked : Int a, Int a -> Result (Int a) [DivByZero]*
divCeilChecked = \a, b ->
if b == 0 then
Err DivByZero
else
Ok (Num.divCeil a b)
## Divide two integers, truncating the result towards zero.
##
@ -769,7 +824,13 @@ divCeilChecked : Int a, Int a -> Result (Int a) [DivByZero]*
## >>> Num.divTrunc 8 -3
##
divTrunc : Int a, Int a -> Int a
divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]*
divTruncChecked = \a, b ->
if b == 0 then
Err DivByZero
else
Ok (Num.divTrunc a b)
## Obtain the remainder (truncating modulo) from the division of two integers.
##
@ -783,7 +844,13 @@ divTruncChecked : Int a, Int a -> Result (Int a) [DivByZero]*
##
## >>> Num.rem -8 -3
rem : Int a, Int a -> Int a
remChecked : Int a, Int a -> Result (Int a) [DivByZero]*
remChecked = \a, b ->
if b == 0 then
Err DivByZero
else
Ok (Num.rem a b)
isMultipleOf : Int a, Int a -> Bool
@ -842,6 +909,15 @@ addSaturated : Num a, Num a -> Num a
## This is the same as [Num.add] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
addChecked : Num a, Num a -> Result (Num a) [Overflow]*
addChecked = \a, b ->
result = addCheckedLowlevel a b
if result.b then
Err Overflow
else
Ok result.a
addCheckedLowlevel : Num a, Num a -> { b : Bool, a : Num a }
subWrap : Int range, Int range -> Int range
@ -859,6 +935,15 @@ subSaturated : Num a, Num a -> Num a
## This is the same as [Num.sub] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
subChecked : Num a, Num a -> Result (Num a) [Overflow]*
subChecked = \a, b ->
result = subCheckedLowlevel a b
if result.b then
Err Overflow
else
Ok result.a
subCheckedLowlevel : Num a, Num a -> { b : Bool, a : Num a }
mulWrap : Int range, Int range -> Int range
@ -874,6 +959,15 @@ mulSaturated : Num a, Num a -> Num a
## This is the same as [Num.mul] except if the operation overflows, instead of
## panicking or returning ∞ or -∞, it will return `Err Overflow`.
mulChecked : Num a, Num a -> Result (Num a) [Overflow]*
mulChecked = \a, b ->
result = mulCheckedLowlevel a b
if result.b then
Err Overflow
else
Ok result.a
mulCheckedLowlevel : Num a, Num a -> { b : Bool, a : Num a }
## The lowest number that can be stored in an [I8] without underflowing its
## available memory and crashing.

View file

@ -1,5 +1,5 @@
interface Result
exposes [Result, isOk, isErr, map, mapErr, after, withDefault]
exposes [Result, isOk, isErr, map, mapErr, after, afterErr, withDefault]
imports [Bool.{ Bool }]
## The result of an operation that could fail: either the operation went
@ -92,3 +92,19 @@ after = \result, transform ->
transform v
Err e ->
Err e
## If the result is `Err`, transform the entire result by running a conversion
## function on the value the `Err` holds. Then return that new result.
##
## (If the result is `Ok`, this has no effect. Use `after` to transform an `Ok`.)
##
## >>> Result.afterErr (Ok 10) \errorNum -> Str.toNat errorNum
##
## >>> Result.afterErr (Err "42") \errorNum -> Str.toNat errorNum
afterErr : Result a err, (err -> Result a otherErr) -> Result a otherErr
afterErr = \result, transform ->
when result is
Ok v ->
Ok v
Err e ->
transform e

View file

@ -24,10 +24,19 @@ single : k -> Set k
## retrieved or removed from the [Set].
insert : Set k, k -> Set k
len : Set k -> Nat
len = \set ->
set
|> Set.toDict
|> Dict.len
## Drops the given element from the set.
remove : Set k, k -> Set k
contains : Set k, k -> Bool
contains = \set, key ->
set
|> Set.toDict
|> Dict.contains key
# toList = \set -> Dict.keys (toDict set)
toList : Set k -> List k

View file

@ -199,10 +199,35 @@ toScalars : Str -> List U32
## >>> Str.toUtf8 "🐦"
toUtf8 : Str -> List U8
# fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8Problem]*
# fromUtf8Range : List U8 -> Result Str [BadUtf8 Utf8Problem Nat, OutOfBounds]*
fromUtf8 : List U8 -> Result Str [BadUtf8 Utf8ByteProblem Nat]*
fromUtf8 = \bytes ->
result = fromUtf8RangeLowlevel bytes 0 (List.len bytes)
if result.cIsOk then
Ok result.bString
else
Err (BadUtf8 result.dProblemCode result.aByteIndex)
fromUtf8Range : List U8, { start : Nat, count : Nat } -> Result Str [BadUtf8 Utf8ByteProblem Nat, OutOfBounds]*
fromUtf8Range = \bytes, config ->
if config.start + config.count <= List.len bytes then
result = fromUtf8RangeLowlevel bytes config.start config.count
if result.cIsOk then
Ok result.bString
else
Err (BadUtf8 result.dProblemCode result.aByteIndex)
else
Err OutOfBounds
FromUtf8Result : {
aByteIndex : Nat,
bString : Str,
cIsOk : Bool,
dProblemCode : Utf8ByteProblem,
}
fromUtf8RangeLowlevel : List U8, Nat, Nat -> FromUtf8Result
startsWith : Str, Str -> Bool
endsWith : Str, Str -> Bool
@ -214,19 +239,33 @@ trimLeft : Str -> Str
trimRight : Str -> Str
toDec : Str -> Result Dec [InvalidNumStr]*
toDec = \string -> strToNumHelp string
toF64 : Str -> Result F64 [InvalidNumStr]*
toF64 = \string -> strToNumHelp string
toF32 : Str -> Result F32 [InvalidNumStr]*
toF32 = \string -> strToNumHelp string
toNat : Str -> Result Nat [InvalidNumStr]*
toNat = \string -> strToNumHelp string
toU128 : Str -> Result U128 [InvalidNumStr]*
toU128 = \string -> strToNumHelp string
toI128 : Str -> Result I128 [InvalidNumStr]*
toI128 = \string -> strToNumHelp string
toU64 : Str -> Result U64 [InvalidNumStr]*
toU64 = \string -> strToNumHelp string
toI64 : Str -> Result I64 [InvalidNumStr]*
toI64 = \string -> strToNumHelp string
toU32 : Str -> Result U32 [InvalidNumStr]*
toU32 = \string -> strToNumHelp string
toI32 : Str -> Result I32 [InvalidNumStr]*
toI32 = \string -> strToNumHelp string
toU16 : Str -> Result U16 [InvalidNumStr]*
toU16 = \string -> strToNumHelp string
toI16 : Str -> Result I16 [InvalidNumStr]*
toI16 = \string -> strToNumHelp string
toU8 : Str -> Result U8 [InvalidNumStr]*
toU8 = \string -> strToNumHelp string
toI8 : Str -> Result I8 [InvalidNumStr]*
toI8 = \string -> strToNumHelp string
## Gets the byte at the given index, without performing a bounds check
getUnsafe : Str, Nat -> U8
@ -393,3 +432,15 @@ walkScalarsUntilHelp = \string, state, step, index, length ->
newState
else
state
strToNum : Str -> { berrorcode : U8, aresult : Num * }
strToNumHelp : Str -> Result (Num a) [InvalidNumStr]*
strToNumHelp = \string ->
result : { berrorcode : U8, aresult : Num a }
result = strToNum string
if result.berrorcode == 0 then
Ok result.aresult
else
Err InvalidNumStr

View file

@ -326,7 +326,6 @@ pub const STR_TO_DECIMAL: &str = "roc_builtins.str.to_decimal";
pub const STR_EQUAL: &str = "roc_builtins.str.equal";
pub const STR_SUBSTRING_UNSAFE: &str = "roc_builtins.str.substring_unsafe";
pub const STR_TO_UTF8: &str = "roc_builtins.str.to_utf8";
pub const STR_FROM_UTF8: &str = "roc_builtins.str.from_utf8";
pub const STR_FROM_UTF8_RANGE: &str = "roc_builtins.str.from_utf8_range";
pub const STR_REPEAT: &str = "roc_builtins.str.repeat";
pub const STR_TRIM: &str = "roc_builtins.str.trim";

File diff suppressed because it is too large Load diff

View file

@ -13,6 +13,12 @@ impl<T> Default for VecSet<T> {
}
}
impl<T> VecSet<T> {
pub fn into_vec(self) -> Vec<T> {
self.elements
}
}
impl<T: PartialEq> VecSet<T> {
pub fn with_capacity(capacity: usize) -> Self {
Self {

View file

@ -143,6 +143,7 @@ fn constrain_untyped_closure(
constraints,
name,
region,
fn_var,
captured_symbols,
closure_var,
&mut vars,
@ -911,6 +912,7 @@ pub fn constrain_expr(
let lambda_set = Type::ClosureTag {
name: *closure_name,
captures: vec![],
ambient_function: *function_var,
};
let closure_type = Type::Variable(*closure_var);
@ -1390,6 +1392,7 @@ fn constrain_function_def(
constraints,
loc_symbol.value,
region,
expr_var,
&function_def.captured_symbols,
closure_var,
&mut vars,
@ -1981,13 +1984,13 @@ fn constrain_typed_def(
);
def_pattern_state.constraints.push(constraints.equal_types(
expr_type,
expr_type.clone(),
annotation_expected,
Category::Storage(std::file!(), std::line!()),
Region::span_across(&annotation.region, &def.loc_expr.region),
));
// when a def is annotated, and it's body is a closure, treat this
// when a def is annotated, and its body is a closure, treat this
// as a named function (in elm terms) for error messages.
//
// This means we get errors like "the first argument of `f` is weird"
@ -2040,6 +2043,7 @@ fn constrain_typed_def(
constraints,
*name,
region,
*fn_var,
captured_symbols,
closure_var,
&mut vars,
@ -2114,7 +2118,7 @@ fn constrain_typed_def(
AnnotationSource::TypedBody {
region: annotation.region,
},
signature.clone(),
expr_type,
);
let ret_constraint = constrain_expr(
@ -2124,14 +2128,7 @@ fn constrain_typed_def(
&def.loc_expr.value,
annotation_expected,
);
let ret_constraint = attach_resolution_constraints(constraints, env, ret_constraint);
let cons = [
ret_constraint,
// Store type into AST vars. We use Store so errors aren't reported twice
constraints.store(signature, expr_var, std::file!(), std::line!()),
];
let expr_con = constraints.and_constraint(cons);
let expr_con = attach_resolution_constraints(constraints, env, ret_constraint);
constrain_def_make_constraint(
constraints,
@ -2514,6 +2511,7 @@ fn constrain_closure_size(
constraints: &mut Constraints,
name: Symbol,
region: Region,
ambient_function: Variable,
captured_symbols: &[(Symbol, Variable)],
closure_var: Variable,
variables: &mut Vec<Variable>,
@ -2542,6 +2540,7 @@ fn constrain_closure_size(
let closure_type = Type::ClosureTag {
name,
captures: captured_types,
ambient_function,
};
let finalizer = constraints.equal_types_var(
@ -2811,6 +2810,7 @@ fn constraint_recursive_function(
constraints,
loc_symbol.value,
region,
expr_var,
&function_def.captured_symbols,
closure_var,
&mut vars,
@ -3220,6 +3220,7 @@ fn rec_defs_help(
constraints,
*name,
region,
*fn_var,
captured_symbols,
closure_var,
&mut vars,

View file

@ -65,6 +65,9 @@ flags! {
/// Only use this in single-threaded mode!
ROC_PRINT_UNIFICATIONS
/// Prints traces of unspecialized lambda set compaction
ROC_TRACE_COMPACTION
/// Like ROC_PRINT_UNIFICATIONS, in the context of typechecking derived implementations.
/// Only use this in single-threaded mode!
ROC_PRINT_UNIFICATIONS_DERIVED

View file

@ -325,6 +325,9 @@ fn to_encoder_string(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
let (body, this_encoder_var) =
wrap_in_encode_custom(env, encode_string_call, encoder_var, s_sym, Variable::STR);
// Create fn_var for ambient capture; we fix it up below.
let fn_var = synth_var(env.subs, Content::Error);
// -[fn_name]->
let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, once((fn_name, vec![])));
let fn_clos_var = synth_var(
@ -333,11 +336,12 @@ fn to_encoder_string(env: &mut Env<'_>, fn_name: Symbol) -> (Expr, Variable) {
solved: fn_name_labels,
recursion_var: OptVariable::NONE,
unspecialized: SubsSlice::default(),
ambient_function: fn_var,
}),
);
// Str -[fn_name]-> (typeof Encode.record [ .. ] = Encoder fmt)
let fn_var = synth_var(
env.subs,
env.subs.set_content(
fn_var,
Content::Structure(FlatType::Func(
string_var_slice,
fn_clos_var,
@ -527,6 +531,9 @@ fn to_encoder_record(
let (body, this_encoder_var) =
wrap_in_encode_custom(env, encode_record_call, encoder_var, rcd_sym, record_var);
// Create fn_var for ambient capture; we fix it up below.
let fn_var = synth_var(env.subs, Content::Error);
// -[fn_name]->
let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, once((fn_name, vec![])));
let fn_clos_var = synth_var(
@ -535,12 +542,13 @@ fn to_encoder_record(
solved: fn_name_labels,
recursion_var: OptVariable::NONE,
unspecialized: SubsSlice::default(),
ambient_function: fn_var,
}),
);
// typeof rcd -[fn_name]-> (typeof Encode.record [ .. ] = Encoder fmt)
let record_var_slice = SubsSlice::insert_into_subs(env.subs, once(record_var));
let fn_var = synth_var(
env.subs,
env.subs.set_content(
fn_var,
Content::Structure(FlatType::Func(
record_var_slice,
fn_clos_var,
@ -758,6 +766,9 @@ fn to_encoder_tag_union(
tag_union_var,
);
// Create fn_var for ambient capture; we fix it up below.
let fn_var = synth_var(env.subs, Content::Error);
// -[fn_name]->
let fn_name_labels = UnionLambdas::insert_into_subs(env.subs, once((fn_name, vec![])));
let fn_clos_var = synth_var(
@ -766,12 +777,13 @@ fn to_encoder_tag_union(
solved: fn_name_labels,
recursion_var: OptVariable::NONE,
unspecialized: SubsSlice::default(),
ambient_function: fn_var,
}),
);
// tag_union_var -[fn_name]-> whole_tag_encoders_var
let tag_union_var_slice = SubsSlice::insert_into_subs(env.subs, once(tag_union_var));
let fn_var = synth_var(
env.subs,
env.subs.set_content(
fn_var,
Content::Structure(FlatType::Func(
tag_union_var_slice,
fn_clos_var,
@ -871,6 +883,9 @@ fn wrap_in_encode_custom(
CalledVia::Space,
);
// Create fn_var for ambient capture; we fix it up below.
let fn_var = synth_var(env.subs, Content::Error);
// -[[FN_name captured_var]]->
let fn_name_labels =
UnionLambdas::insert_into_subs(env.subs, once((fn_name, vec![captured_var])));
@ -880,13 +895,14 @@ fn wrap_in_encode_custom(
solved: fn_name_labels,
recursion_var: OptVariable::NONE,
unspecialized: SubsSlice::default(),
ambient_function: fn_var,
}),
);
// bytes, fmt -[[FN_name captured_var]]-> Encode.appendWith bytes encoder fmt
let args_slice = SubsSlice::insert_into_subs(env.subs, vec![bytes_var, fmt_var]);
let fn_var = synth_var(
env.subs,
env.subs.set_content(
fn_var,
Content::Structure(FlatType::Func(args_slice, fn_clos_var, Variable::LIST_U8)),
);

View file

@ -11,9 +11,9 @@ use crate::llvm::build_list::{
self, allocate_list, empty_polymorphic_list, list_append_unsafe, list_concat, list_drop_at,
list_get_unsafe, list_len, list_map, list_map2, list_map3, list_map4, list_prepend,
list_replace_unsafe, list_reserve, list_sort_with, list_sublist, list_swap,
list_symbol_to_c_abi, list_to_c_abi, list_with_capacity,
list_symbol_to_c_abi, list_to_c_abi, list_with_capacity, pass_update_mode,
};
use crate::llvm::build_str::{str_from_float, str_from_int, str_from_utf8, str_from_utf8_range};
use crate::llvm::build_str::{str_from_float, str_from_int};
use crate::llvm::compare::{generic_eq, generic_neq};
use crate::llvm::convert::{
self, argument_type_from_layout, basic_type_from_builtin, basic_type_from_layout,
@ -3629,7 +3629,11 @@ fn expose_function_to_host_help_c_abi_v2<'a, 'ctx, 'env>(
let (params, param_types) = match (&roc_return, &cc_return) {
// Drop the "return pointer" if it exists on the roc function
// and the c function does not return via pointer
(RocReturn::ByPointer, CCReturn::Return) => (&params[..], &param_types[1..]),
(RocReturn::ByPointer, CCReturn::Return) => {
// Roc currently puts the return pointer at the end of the argument list.
// As such, we drop the last element here instead of the first.
(&params[..], &param_types[..param_types.len() - 1])
}
// Drop the return pointer the other way, if the C function returns by pointer but Roc
// doesn't
(RocReturn::Return, CCReturn::ByPointer) => (&params[1..], &param_types[..]),
@ -5354,18 +5358,31 @@ fn run_low_level<'a, 'ctx, 'env>(
str_from_float(env, scope, args[0])
}
StrFromUtf8 => {
// Str.fromUtf8 : List U8 -> Result Str Utf8Problem
debug_assert_eq!(args.len(), 1);
str_from_utf8(env, scope, args[0], update_mode)
}
StrFromUtf8Range => {
debug_assert_eq!(args.len(), 2);
debug_assert_eq!(args.len(), 3);
let count_and_start = load_symbol(scope, &args[1]).into_struct_value();
let list = args[0];
let start = load_symbol(scope, &args[1]);
let count = load_symbol(scope, &args[2]);
str_from_utf8_range(env, scope, args[0], count_and_start)
let result_type = env.module.get_struct_type("str.FromUtf8Result").unwrap();
let result_ptr = env
.builder
.build_alloca(result_type, "alloca_utf8_validate_bytes_result");
call_void_bitcode_fn(
env,
&[
result_ptr.into(),
list_symbol_to_c_abi(env, scope, list).into(),
start,
count,
pass_update_mode(env, update_mode),
],
bitcode::STR_FROM_UTF8_RANGE,
);
crate::llvm::build_str::decode_from_utf8_result(env, result_ptr).into()
}
StrToUtf8 => {
// Str.fromInt : Str -> List U8
@ -5549,10 +5566,6 @@ fn run_low_level<'a, 'ctx, 'env>(
)
}
ListSublist => {
// List.sublist : List elem, { start : Nat, len : Nat } -> List elem
//
// As a low-level, record is destructed
// List.sublist : List elem, start : Nat, len : Nat -> List elem
debug_assert_eq!(args.len(), 3);
let (list, list_layout) = load_symbol_and_layout(scope, &args[0]);

View file

@ -1,17 +1,14 @@
use crate::llvm::bitcode::{call_bitcode_fn, call_str_bitcode_fn, call_void_bitcode_fn};
use crate::llvm::bitcode::{call_bitcode_fn, call_str_bitcode_fn};
use crate::llvm::build::{Env, Scope};
use crate::llvm::build_list::pass_update_mode;
use inkwell::builder::Builder;
use inkwell::values::{BasicValueEnum, IntValue, PointerValue, StructValue};
use inkwell::AddressSpace;
use morphic_lib::UpdateMode;
use roc_builtins::bitcode::{self, IntWidth};
use roc_module::symbol::Symbol;
use roc_mono::layout::{Builtin, Layout};
use roc_target::PtrWidth;
use super::build::{create_entry_block_alloca, load_symbol};
use super::build_list::list_symbol_to_c_abi;
pub static CHAR_LAYOUT: Layout = Layout::u8();
@ -70,7 +67,7 @@ pub fn str_from_int<'a, 'ctx, 'env>(
call_str_bitcode_fn(env, &[value.into()], &bitcode::STR_FROM_INT[int_width])
}
fn decode_from_utf8_result<'a, 'ctx, 'env>(
pub fn decode_from_utf8_result<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
pointer: PointerValue<'ctx>,
) -> StructValue<'ctx> {
@ -106,67 +103,6 @@ fn decode_from_utf8_result<'a, 'ctx, 'env>(
}
}
/// Str.fromUtf8 : List U8, { count : Nat, start : Nat } -> { a : Bool, b : Str, c : Nat, d : I8 }
pub fn str_from_utf8_range<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
list: Symbol,
count_and_start: StructValue<'ctx>,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let result_type = env.module.get_struct_type("str.FromUtf8Result").unwrap();
let result_ptr = builder.build_alloca(result_type, "alloca_utf8_validate_bytes_result");
let count = env
.builder
.build_extract_value(count_and_start, 0, "get_count")
.unwrap();
let start = env
.builder
.build_extract_value(count_and_start, 1, "get_start")
.unwrap();
call_void_bitcode_fn(
env,
&[
result_ptr.into(),
list_symbol_to_c_abi(env, scope, list).into(),
count,
start,
],
bitcode::STR_FROM_UTF8_RANGE,
);
decode_from_utf8_result(env, result_ptr).into()
}
/// Str.fromUtf8 : List U8 -> { a : Bool, b : Str, c : Nat, d : I8 }
pub fn str_from_utf8<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
scope: &Scope<'a, 'ctx>,
list: Symbol,
update_mode: UpdateMode,
) -> BasicValueEnum<'ctx> {
let builder = env.builder;
let result_type = env.module.get_struct_type("str.FromUtf8Result").unwrap();
let result_ptr = builder.build_alloca(result_type, "alloca_utf8_validate_bytes_result");
call_void_bitcode_fn(
env,
&[
result_ptr.into(),
list_symbol_to_c_abi(env, scope, list).into(),
pass_update_mode(env, update_mode),
],
bitcode::STR_FROM_UTF8,
);
decode_from_utf8_result(env, result_ptr).into()
}
/// Str.fromFloat : Int -> Str
pub fn str_from_float<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,

View file

@ -1595,7 +1595,8 @@ impl<'a> WasmBackend<'a> {
// Store the tag ID (if any)
if stores_tag_id_as_data {
let id_offset = data_offset + data_size - data_alignment;
let id_offset =
data_offset + union_layout.data_size_without_tag_id(TARGET_INFO).unwrap();
let id_align = union_layout.tag_id_builtin().alignment_bytes(TARGET_INFO);
let id_align = Align::from(id_align);
@ -1678,8 +1679,7 @@ impl<'a> WasmBackend<'a> {
};
if union_layout.stores_tag_id_as_data(TARGET_INFO) {
let (data_size, data_alignment) = union_layout.data_size_and_alignment(TARGET_INFO);
let id_offset = data_size - data_alignment;
let id_offset = union_layout.data_size_without_tag_id(TARGET_INFO).unwrap();
let id_align = union_layout.tag_id_builtin().alignment_bytes(TARGET_INFO);
let id_align = Align::from(id_align);

View file

@ -264,16 +264,19 @@ impl<'a> LowLevelCall<'a> {
}
StrFromInt => self.num_to_str(backend),
StrFromFloat => self.num_to_str(backend),
StrFromUtf8 => {
StrFromUtf8Range => {
/*
Low-level op returns a struct with all the data for both Ok and Err.
Roc AST wrapper converts this to a tag union, with app-dependent tag IDs.
fromUtf8C(output: *FromUtf8Result, arg: RocList, update_mode: UpdateMode) callconv(.C) void
output: *FromUtf8Result i32
arg: RocList i64, i32
start i32
count i32
update_mode: UpdateMode i32
*/
// loads arg, start, count
backend.storage.load_symbols_for_call(
backend.env.arena,
&mut backend.code_builder,
@ -283,9 +286,8 @@ impl<'a> LowLevelCall<'a> {
CallConv::Zig,
);
backend.code_builder.i32_const(UPDATE_MODE_IMMUTABLE);
backend.call_host_fn_after_loading_args(bitcode::STR_FROM_UTF8, 4, false);
backend.call_host_fn_after_loading_args(bitcode::STR_FROM_UTF8_RANGE, 6, false);
}
StrFromUtf8Range => self.load_args_and_call_zig(backend, bitcode::STR_FROM_UTF8_RANGE),
StrTrimLeft => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_LEFT),
StrTrimRight => self.load_args_and_call_zig(backend, bitcode::STR_TRIM_RIGHT),
StrToUtf8 => self.load_args_and_call_zig(backend, bitcode::STR_TO_UTF8),

View file

@ -9,7 +9,7 @@ use roc_collections::MutMap;
use roc_derive_key::GlobalDerivedSymbols;
use roc_module::symbol::ModuleId;
use roc_solve::solve::{compact_lambda_sets_of_vars, Phase, Pools};
use roc_types::subs::Content;
use roc_types::subs::{Content, FlatType, LambdaSet};
use roc_types::subs::{ExposedTypesStorageSubs, Subs, Variable};
use roc_unify::unify::{unify as unify_unify, Mode, Unified};
@ -91,7 +91,7 @@ impl Phase for LatePhase<'_> {
}
#[inline(always)]
fn copy_lambda_set_var_to_home_subs(
fn copy_lambda_set_ambient_function_to_home_subs(
&self,
external_lambda_set_var: Variable,
external_module_id: ModuleId,
@ -99,11 +99,12 @@ impl Phase for LatePhase<'_> {
) -> Variable {
match (external_module_id == self.home, self.abilities) {
(true, _) | (false, AbilitiesView::Module(_)) => {
debug_assert!(matches!(
target_subs.get_content_without_compacting(external_lambda_set_var),
Content::LambdaSet(..)
));
external_lambda_set_var
// The lambda set (and hence its ambient function) should be available in the
// current subs.
let LambdaSet {
ambient_function, ..
} = target_subs.get_lambda_set(external_lambda_set_var);
ambient_function
}
(false, AbilitiesView::World(wa)) => {
let mut world = wa.world.write().unwrap();
@ -113,17 +114,27 @@ impl Phase for LatePhase<'_> {
.stored_specialization_lambda_set_vars
.get(&external_lambda_set_var)
.unwrap();
let LambdaSet {
ambient_function, ..
} = module_types
.storage_subs
.as_inner()
.get_lambda_set(storage_lambda_set_var);
let copied = module_types
.storage_subs
.export_variable_to(target_subs, storage_lambda_set_var);
let our_lambda_set_var = copied.variable;
// TODO: I think this is always okay, but revisit later when we're in a more
// stable position to see if we can get rid of the bookkeeping done as a result
// of this.
.export_variable_to_directly_to_use_site(target_subs, ambient_function);
let our_ambient_function_var = copied.variable;
debug_assert!(matches!(
target_subs.get_content_without_compacting(our_lambda_set_var),
Content::LambdaSet(..)
target_subs.get_content_without_compacting(our_ambient_function_var),
Content::Structure(FlatType::Func(..))
));
our_lambda_set_var
our_ambient_function_var
}
}
}
@ -152,7 +163,7 @@ pub fn unify(
let late_phase = LatePhase { home, abilities };
compact_lambda_sets_of_vars(
let must_implement_constraints = compact_lambda_sets_of_vars(
subs,
arena,
&mut pools,
@ -160,6 +171,11 @@ pub fn unify(
&late_phase,
derived_symbols,
);
// At this point we can't do anything with must-implement constraints, since we're no
// longer solving. We must assume that they were totally caught during solving.
// After we land https://github.com/rtfeldman/roc/issues/3207 this concern should totally
// go away.
let _ = must_implement_constraints;
// Pools are only used to keep track of variable ranks for generalization purposes.
// Since we break generalization during monomorphization, `pools` is irrelevant
// here. We only need it for `compact_lambda_sets_of_vars`, which is also used in a

View file

@ -1,7 +1,7 @@
use std::path::PathBuf;
use bumpalo::Bump;
use roc_load_internal::file::Threading;
use roc_load_internal::file::{LoadingProblem, Threading};
use roc_module::symbol::ModuleId;
const MODULES: &[(ModuleId, &str)] = &[
@ -47,7 +47,16 @@ fn write_subs_for_module(module_id: ModuleId, filename: &str) {
Threading::AllAvailable,
);
let module = res_module.unwrap();
let module = match res_module {
Ok(v) => v,
Err(LoadingProblem::FormattedReport(report)) => {
panic!("{}", report);
}
Err(other) => {
panic!("build_file failed with error:\n{:?}", other);
}
};
let subs = module.solved.inner();
let exposed_vars_by_symbol: Vec<_> = module.exposed_to_host.into_iter().collect();

View file

@ -200,7 +200,9 @@ fn generate_entry_docs<'a>(
ValueDef::Body(_, _) => (),
ValueDef::Expect(c) => todo!("documentation for tests {:?}", c),
ValueDef::Expect(_) => {
// Don't generate docs for `expect`s
}
},
Ok(type_index) => match &defs.type_defs[type_index.index()] {
TypeDef::Alias {

View file

@ -5125,12 +5125,12 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
}
RootIsInterface => {
let doc = alloc.stack([
alloc.reflow(r"The input file is an interface module, but only app modules can be ran."),
alloc.concat([
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "),
alloc.reflow(r"but won't output any executable."),
])
]);
alloc.reflow(r"The input file is an interface module, but only app modules can be run."),
alloc.concat([
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),
@ -5141,12 +5141,12 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
}
RootIsHosted => {
let doc = alloc.stack([
alloc.reflow(r"The input file is a hosted module, but only app modules can be ran."),
alloc.concat([
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "),
alloc.reflow(r"but won't output any executable."),
])
]);
alloc.reflow(r"The input file is a hosted module, but only app modules can be run."),
alloc.concat([
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),
@ -5157,12 +5157,12 @@ fn to_missing_platform_report(module_id: ModuleId, other: PlatformPath) -> Strin
}
RootIsPlatformModule => {
let doc = alloc.stack([
alloc.reflow(r"The input file is a package config file, but only app modules can be ran."),
alloc.concat([
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "),
alloc.reflow(r"but won't output any executable."),
])
]);
alloc.reflow(r"The input file is a package config file, but only app modules can be run."),
alloc.concat([
alloc.reflow(r"I will still parse and typecheck the input file and its dependencies, "),
alloc.reflow(r"but won't output any executable."),
])
]);
Report {
filename: "UNKNOWN.roc".into(),

View file

@ -15,7 +15,6 @@ pub enum LowLevel {
StrCountGraphemes,
StrCountUtf8Bytes,
StrFromInt,
StrFromUtf8,
StrFromUtf8Range,
StrToUtf8,
StrRepeat,
@ -161,141 +160,181 @@ impl LowLevel {
pub enum LowLevelWrapperType {
/// This wrapper function contains no logic and we can remove it in code gen
CanBeReplacedBy(LowLevel),
/// This wrapper function contains important logic and we cannot remove it in code gen
WrapperIsRequired,
NotALowLevelWrapper,
}
impl LowLevelWrapperType {
pub fn from_symbol(symbol: Symbol) -> LowLevelWrapperType {
use LowLevel::*;
use LowLevelWrapperType::*;
match symbol {
Symbol::STR_CONCAT => CanBeReplacedBy(StrConcat),
Symbol::STR_GET_UNSAFE => CanBeReplacedBy(StrGetUnsafe),
Symbol::STR_TO_SCALARS => CanBeReplacedBy(StrToScalars),
Symbol::STR_JOIN_WITH => CanBeReplacedBy(StrJoinWith),
Symbol::STR_IS_EMPTY => CanBeReplacedBy(StrIsEmpty),
Symbol::STR_STARTS_WITH => CanBeReplacedBy(StrStartsWith),
Symbol::STR_STARTS_WITH_SCALAR => CanBeReplacedBy(StrStartsWithScalar),
Symbol::STR_ENDS_WITH => CanBeReplacedBy(StrEndsWith),
Symbol::STR_SPLIT => CanBeReplacedBy(StrSplit),
Symbol::STR_COUNT_GRAPHEMES => CanBeReplacedBy(StrCountGraphemes),
Symbol::STR_COUNT_UTF8_BYTES => CanBeReplacedBy(StrCountUtf8Bytes),
Symbol::STR_FROM_UTF8 => WrapperIsRequired,
Symbol::STR_FROM_UTF8_RANGE => WrapperIsRequired,
Symbol::STR_TO_UTF8 => CanBeReplacedBy(StrToUtf8),
Symbol::STR_REPEAT => CanBeReplacedBy(StrRepeat),
Symbol::STR_RESERVE => CanBeReplacedBy(StrReserve),
Symbol::STR_APPEND_SCALAR_UNSAFE => CanBeReplacedBy(StrAppendScalar),
Symbol::STR_TRIM => CanBeReplacedBy(StrTrim),
Symbol::STR_TRIM_LEFT => CanBeReplacedBy(StrTrimLeft),
Symbol::STR_TRIM_RIGHT => CanBeReplacedBy(StrTrimRight),
Symbol::STR_TO_DEC => WrapperIsRequired,
Symbol::STR_TO_F64 => WrapperIsRequired,
Symbol::STR_TO_F32 => WrapperIsRequired,
Symbol::STR_TO_NAT => WrapperIsRequired,
Symbol::STR_TO_U128 => WrapperIsRequired,
Symbol::STR_TO_I128 => WrapperIsRequired,
Symbol::STR_TO_U64 => WrapperIsRequired,
Symbol::STR_TO_I64 => WrapperIsRequired,
Symbol::STR_TO_U32 => WrapperIsRequired,
Symbol::STR_TO_I32 => WrapperIsRequired,
Symbol::STR_TO_U16 => WrapperIsRequired,
Symbol::STR_TO_I16 => WrapperIsRequired,
Symbol::STR_TO_U8 => WrapperIsRequired,
Symbol::STR_TO_I8 => WrapperIsRequired,
Symbol::LIST_LEN => CanBeReplacedBy(ListLen),
Symbol::LIST_GET => WrapperIsRequired,
Symbol::LIST_REPLACE => WrapperIsRequired,
Symbol::LIST_CONCAT => CanBeReplacedBy(ListConcat),
Symbol::LIST_APPEND_UNSAFE => CanBeReplacedBy(ListAppendUnsafe),
Symbol::LIST_PREPEND => CanBeReplacedBy(ListPrepend),
Symbol::LIST_MAP => WrapperIsRequired,
Symbol::LIST_MAP2 => WrapperIsRequired,
Symbol::LIST_MAP3 => WrapperIsRequired,
Symbol::LIST_MAP4 => WrapperIsRequired,
Symbol::LIST_SORT_WITH => WrapperIsRequired,
Symbol::LIST_SUBLIST => WrapperIsRequired,
Symbol::LIST_DROP_AT => CanBeReplacedBy(ListDropAt),
Symbol::LIST_SWAP => CanBeReplacedBy(ListSwap),
Symbol::LIST_ANY => WrapperIsRequired,
Symbol::LIST_ALL => WrapperIsRequired,
Symbol::LIST_FIND => WrapperIsRequired,
Symbol::DICT_LEN => CanBeReplacedBy(DictSize),
Symbol::DICT_EMPTY => CanBeReplacedBy(DictEmpty),
Symbol::DICT_INSERT => CanBeReplacedBy(DictInsert),
Symbol::DICT_REMOVE => CanBeReplacedBy(DictRemove),
Symbol::DICT_CONTAINS => CanBeReplacedBy(DictContains),
Symbol::DICT_GET => WrapperIsRequired,
Symbol::DICT_KEYS => CanBeReplacedBy(DictKeys),
Symbol::DICT_VALUES => CanBeReplacedBy(DictValues),
Symbol::DICT_UNION => CanBeReplacedBy(DictUnion),
Symbol::DICT_INTERSECTION => CanBeReplacedBy(DictIntersection),
Symbol::DICT_DIFFERENCE => CanBeReplacedBy(DictDifference),
Symbol::DICT_WALK => WrapperIsRequired,
Symbol::SET_FROM_LIST => CanBeReplacedBy(SetFromList),
Symbol::NUM_ADD => CanBeReplacedBy(NumAdd),
Symbol::NUM_ADD_WRAP => CanBeReplacedBy(NumAddWrap),
Symbol::NUM_ADD_CHECKED => WrapperIsRequired,
Symbol::NUM_ADD_SATURATED => CanBeReplacedBy(NumAddSaturated),
Symbol::NUM_SUB => CanBeReplacedBy(NumSub),
Symbol::NUM_SUB_WRAP => CanBeReplacedBy(NumSubWrap),
Symbol::NUM_SUB_CHECKED => WrapperIsRequired,
Symbol::NUM_SUB_SATURATED => CanBeReplacedBy(NumSubSaturated),
Symbol::NUM_MUL => CanBeReplacedBy(NumMul),
Symbol::NUM_MUL_WRAP => CanBeReplacedBy(NumMulWrap),
Symbol::NUM_MUL_SATURATED => CanBeReplacedBy(NumMulSaturated),
Symbol::NUM_MUL_CHECKED => WrapperIsRequired,
Symbol::NUM_GT => CanBeReplacedBy(NumGt),
Symbol::NUM_GTE => CanBeReplacedBy(NumGte),
Symbol::NUM_LT => CanBeReplacedBy(NumLt),
Symbol::NUM_LTE => CanBeReplacedBy(NumLte),
Symbol::NUM_COMPARE => CanBeReplacedBy(NumCompare),
Symbol::NUM_DIV_FRAC => CanBeReplacedBy(NumDivUnchecked),
Symbol::NUM_DIV_FRAC_CHECKED => WrapperIsRequired,
Symbol::NUM_DIV_CEIL => CanBeReplacedBy(NumDivCeilUnchecked),
Symbol::NUM_DIV_CEIL_CHECKED => WrapperIsRequired,
Symbol::NUM_REM => CanBeReplacedBy(NumRemUnchecked),
Symbol::NUM_REM_CHECKED => WrapperIsRequired,
Symbol::NUM_IS_MULTIPLE_OF => CanBeReplacedBy(NumIsMultipleOf),
Symbol::NUM_ABS => CanBeReplacedBy(NumAbs),
Symbol::NUM_NEG => CanBeReplacedBy(NumNeg),
Symbol::NUM_SIN => CanBeReplacedBy(NumSin),
Symbol::NUM_COS => CanBeReplacedBy(NumCos),
Symbol::NUM_SQRT => CanBeReplacedBy(NumSqrtUnchecked),
Symbol::NUM_SQRT_CHECKED => WrapperIsRequired,
Symbol::NUM_LOG => CanBeReplacedBy(NumLogUnchecked),
Symbol::NUM_LOG_CHECKED => WrapperIsRequired,
Symbol::NUM_ROUND => CanBeReplacedBy(NumRound),
Symbol::NUM_TO_FRAC => CanBeReplacedBy(NumToFrac),
Symbol::NUM_POW => CanBeReplacedBy(NumPow),
Symbol::NUM_CEILING => CanBeReplacedBy(NumCeiling),
Symbol::NUM_POW_INT => CanBeReplacedBy(NumPowInt),
Symbol::NUM_FLOOR => CanBeReplacedBy(NumFloor),
Symbol::NUM_TO_STR => CanBeReplacedBy(NumToStr),
// => CanBeReplacedBy(NumIsFinite),
Symbol::NUM_ATAN => CanBeReplacedBy(NumAtan),
Symbol::NUM_ACOS => CanBeReplacedBy(NumAcos),
Symbol::NUM_ASIN => CanBeReplacedBy(NumAsin),
Symbol::NUM_BYTES_TO_U16 => WrapperIsRequired,
Symbol::NUM_BYTES_TO_U32 => WrapperIsRequired,
Symbol::NUM_BITWISE_AND => CanBeReplacedBy(NumBitwiseAnd),
Symbol::NUM_BITWISE_XOR => CanBeReplacedBy(NumBitwiseXor),
Symbol::NUM_BITWISE_OR => CanBeReplacedBy(NumBitwiseOr),
Symbol::NUM_SHIFT_LEFT => CanBeReplacedBy(NumShiftLeftBy),
Symbol::NUM_SHIFT_RIGHT => CanBeReplacedBy(NumShiftRightBy),
Symbol::NUM_SHIFT_RIGHT_ZERO_FILL => CanBeReplacedBy(NumShiftRightZfBy),
Symbol::NUM_INT_CAST => CanBeReplacedBy(NumIntCast),
Symbol::BOOL_EQ => CanBeReplacedBy(Eq),
Symbol::BOOL_NEQ => CanBeReplacedBy(NotEq),
Symbol::BOOL_AND => CanBeReplacedBy(And),
Symbol::BOOL_OR => CanBeReplacedBy(Or),
Symbol::BOOL_NOT => CanBeReplacedBy(Not),
// => CanBeReplacedBy(Hash),
// => CanBeReplacedBy(ExpectTrue),
_ => NotALowLevelWrapper,
}
for_symbol_help(symbol)
}
}
/// We use a rust macro to ensure that every LowLevel gets handled
macro_rules! map_symbol_to_lowlevel {
($($lowlevel:ident <= $symbol:ident),* $(,)?) => {
fn for_symbol_help(symbol: Symbol) -> LowLevelWrapperType {
use $crate::low_level::LowLevelWrapperType::*;
// expands to a big (but non-exhaustive) match on symbols and maps them to a lowlevel
match symbol {
$(
Symbol::$symbol => CanBeReplacedBy(LowLevel::$lowlevel),
)*
_ => NotALowLevelWrapper,
}
}
fn _enforce_exhaustiveness(lowlevel: LowLevel) -> Symbol {
// when adding a new lowlevel, this match will stop being exhaustive, and give a
// compiler error. Most likely, you are adding a new lowlevel that maps directly to a
// symbol. For instance, you want to have `List.foo` to stand for the `ListFoo`
// lowlevel. In that case, see below in the invocation of `map_symbol_to_lowlevel!`
//
// Below, we explicitly handle some exceptions to the pattern where a lowlevel maps
// directly to a symbol. If you are unsure if your lowlevel is an exception, assume
// that it isn't and just see if that works.
match lowlevel {
$(
LowLevel::$lowlevel => Symbol::$symbol,
)*
// these are higher-order lowlevels. these need the surrounding
// function to provide enough type information for code generation
LowLevel::ListMap => unreachable!(),
LowLevel::ListMap2 => unreachable!(),
LowLevel::ListMap3 => unreachable!(),
LowLevel::ListMap4 => unreachable!(),
LowLevel::ListSortWith => unreachable!(),
LowLevel::DictWalk => unreachable!(),
// (un)boxing is handled in a custom way
LowLevel::BoxExpr => unreachable!(),
LowLevel::UnboxExpr => unreachable!(),
// these functions return polymorphic values
LowLevel::NumIntCast => unreachable!(),
LowLevel::NumToFloatCast => unreachable!(),
LowLevel::NumToIntChecked => unreachable!(),
LowLevel::NumToFloatChecked => unreachable!(),
LowLevel::NumDivUnchecked => unreachable!(),
LowLevel::DictEmpty => unreachable!(),
// these are used internally and not tied to a symbol
LowLevel::Hash => unimplemented!(),
LowLevel::PtrCast => unimplemented!(),
LowLevel::RefCountInc => unimplemented!(),
LowLevel::RefCountDec => unimplemented!(),
// these are not implemented, not sure why
LowLevel::StrFromInt => unimplemented!(),
LowLevel::StrFromFloat => unimplemented!(),
LowLevel::NumIsFinite => unimplemented!(),
}
}
};
}
// here is where we actually specify the mapping for the fast majority of cases that follow the
// pattern of a symbol mapping directly to a lowlevel. In other words, most lowlevels (left) are generated
// by only one specific symbol (right)
map_symbol_to_lowlevel! {
StrConcat <= STR_CONCAT,
StrJoinWith <= STR_JOIN_WITH,
StrIsEmpty <= STR_IS_EMPTY,
StrStartsWith <= STR_STARTS_WITH,
StrStartsWithScalar <= STR_STARTS_WITH_SCALAR,
StrEndsWith <= STR_ENDS_WITH,
StrSplit <= STR_SPLIT,
StrCountGraphemes <= STR_COUNT_GRAPHEMES,
StrCountUtf8Bytes <= STR_COUNT_UTF8_BYTES,
StrFromUtf8Range <= STR_FROM_UTF8_RANGE_LOWLEVEL,
StrToUtf8 <= STR_TO_UTF8,
StrRepeat <= STR_REPEAT,
StrTrim <= STR_TRIM,
StrTrimLeft <= STR_TRIM_LEFT,
StrTrimRight <= STR_TRIM_RIGHT,
StrToScalars <= STR_TO_SCALARS,
StrGetUnsafe <= STR_GET_UNSAFE,
StrSubstringUnsafe <= STR_SUBSTRING_UNSAFE,
StrReserve <= STR_RESERVE,
StrAppendScalar <= STR_APPEND_SCALAR_UNSAFE,
StrGetScalarUnsafe <= STR_GET_SCALAR_UNSAFE,
StrToNum <= STR_TO_NUM,
ListLen <= LIST_LEN,
ListWithCapacity <= LIST_WITH_CAPACITY,
ListReserve <= LIST_RESERVE,
ListIsUnique <= LIST_IS_UNIQUE,
ListAppendUnsafe <= LIST_APPEND_UNSAFE,
ListPrepend <= LIST_PREPEND,
ListGetUnsafe <= LIST_GET_UNSAFE,
ListReplaceUnsafe <= LIST_REPLACE_UNSAFE,
ListConcat <= LIST_CONCAT,
ListSublist <= LIST_SUBLIST_LOWLEVEL,
ListDropAt <= LIST_DROP_AT,
ListSwap <= LIST_SWAP,
DictSize <= DICT_LEN,
DictInsert <= DICT_INSERT,
DictRemove <= DICT_REMOVE,
DictContains <= DICT_CONTAINS,
DictGetUnsafe <= DICT_GET_LOWLEVEL,
DictKeys <= DICT_KEYS,
DictValues <= DICT_VALUES,
DictUnion <= DICT_UNION,
DictIntersection <= DICT_INTERSECTION,
DictDifference <= DICT_DIFFERENCE,
SetFromList <= SET_FROM_LIST,
SetToDict <= SET_TO_DICT,
NumAdd <= NUM_ADD,
NumAddWrap <= NUM_ADD_WRAP,
NumAddChecked <= NUM_ADD_CHECKED_LOWLEVEL,
NumAddSaturated <= NUM_ADD_SATURATED,
NumSub <= NUM_SUB,
NumSubWrap <= NUM_SUB_WRAP,
NumSubChecked <= NUM_SUB_CHECKED_LOWLEVEL,
NumSubSaturated <= NUM_SUB_SATURATED,
NumMul <= NUM_MUL,
NumMulWrap <= NUM_MUL_WRAP,
NumMulSaturated <= NUM_MUL_SATURATED,
NumMulChecked <= NUM_MUL_CHECKED_LOWLEVEL,
NumGt <= NUM_GT,
NumGte <= NUM_GTE,
NumLt <= NUM_LT,
NumLte <= NUM_LTE,
NumCompare <= NUM_COMPARE,
NumDivCeilUnchecked <= NUM_DIV_CEIL,
NumRemUnchecked <= NUM_REM,
NumIsMultipleOf <= NUM_IS_MULTIPLE_OF,
NumAbs <= NUM_ABS,
NumNeg <= NUM_NEG,
NumSin <= NUM_SIN,
NumCos <= NUM_COS,
NumSqrtUnchecked <= NUM_SQRT,
NumLogUnchecked <= NUM_LOG,
NumRound <= NUM_ROUND,
NumToFrac <= NUM_TO_FRAC,
NumPow <= NUM_POW,
NumCeiling <= NUM_CEILING,
NumPowInt <= NUM_POW_INT,
NumFloor <= NUM_FLOOR,
NumAtan <= NUM_ATAN,
NumAcos <= NUM_ACOS,
NumAsin <= NUM_ASIN,
NumBytesToU16 <= NUM_BYTES_TO_U16_LOWLEVEL,
NumBytesToU32 <= NUM_BYTES_TO_U32_LOWLEVEL,
NumBitwiseAnd <= NUM_BITWISE_AND,
NumBitwiseXor <= NUM_BITWISE_XOR,
NumBitwiseOr <= NUM_BITWISE_OR,
NumShiftLeftBy <= NUM_SHIFT_LEFT,
NumShiftRightBy <= NUM_SHIFT_RIGHT,
NumShiftRightZfBy <= NUM_SHIFT_RIGHT_ZERO_FILL,
NumToStr <= NUM_TO_STR,
Eq <= BOOL_EQ,
NotEq <= BOOL_NEQ,
And <= BOOL_AND,
Or <= BOOL_OR,
Not <= BOOL_NOT,
Unreachable <= LIST_UNREACHABLE,
}

View file

@ -997,6 +997,8 @@ define_builtins! {
28 DEV_TMP3: "#dev_tmp3"
29 DEV_TMP4: "#dev_tmp4"
30 DEV_TMP5: "#dev_tmp5"
31 ATTR_INVALID: "#attr_invalid"
}
// Fake module for storing derived function symbols
1 DERIVED: "#Derived" => {
@ -1143,6 +1145,11 @@ define_builtins! {
138 NUM_TO_F64_CHECKED: "toF64Checked"
139 NUM_MAX_F64: "maxF64"
140 NUM_MIN_F64: "minF64"
141 NUM_ADD_CHECKED_LOWLEVEL: "addCheckedLowlevel"
142 NUM_SUB_CHECKED_LOWLEVEL: "subCheckedLowlevel"
143 NUM_MUL_CHECKED_LOWLEVEL: "mulCheckedLowlevel"
144 NUM_BYTES_TO_U16_LOWLEVEL: "bytesToU16Lowlevel"
145 NUM_BYTES_TO_U32_LOWLEVEL: "bytesToU32Lowlevel"
}
3 BOOL: "Bool" => {
0 BOOL_BOOL: "Bool" // the Bool.Bool type alias
@ -1205,6 +1212,8 @@ define_builtins! {
44 STR_GET_SCALAR_UNSAFE: "getScalarUnsafe"
45 STR_WALK_SCALARS: "walkScalars"
46 STR_WALK_SCALARS_UNTIL: "walkScalarsUntil"
47 STR_TO_NUM: "strToNum"
48 STR_FROM_UTF8_RANGE_LOWLEVEL: "fromUtf8RangeLowlevel"
}
5 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias
@ -1274,6 +1283,7 @@ define_builtins! {
64 LIST_UNREACHABLE: "unreachable"
65 LIST_RESERVE: "reserve"
66 LIST_APPEND_UNSAFE: "appendUnsafe"
67 LIST_SUBLIST_LOWLEVEL: "sublistLowlevel"
}
6 RESULT: "Result" => {
0 RESULT_RESULT: "Result" // the Result.Result type alias
@ -1287,6 +1297,7 @@ define_builtins! {
6 RESULT_AFTER: "after"
7 RESULT_IS_OK: "isOk"
8 RESULT_IS_ERR: "isErr"
9 RESULT_AFTER_ERR: "afterErr"
}
7 DICT: "Dict" => {
0 DICT_DICT: "Dict" imported // the Dict.Dict type alias
@ -1306,6 +1317,8 @@ define_builtins! {
12 DICT_UNION: "union"
13 DICT_INTERSECTION: "intersection"
14 DICT_DIFFERENCE: "difference"
15 DICT_GET_LOWLEVEL: "getLowlevel"
}
8 SET: "Set" => {
0 SET_SET: "Set" imported // the Set.Set type alias

View file

@ -934,8 +934,7 @@ pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
NumBytesToU32 => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrStartsWith | StrEndsWith => arena.alloc_slice_copy(&[borrowed, borrowed]),
StrStartsWithScalar => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrFromUtf8 => arena.alloc_slice_copy(&[owned]),
StrFromUtf8Range => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrFromUtf8Range => arena.alloc_slice_copy(&[borrowed, irrelevant, irrelevant]),
StrToUtf8 => arena.alloc_slice_copy(&[owned]),
StrRepeat => arena.alloc_slice_copy(&[borrowed, irrelevant]),
StrFromInt | StrFromFloat => arena.alloc_slice_copy(&[irrelevant]),

View file

@ -620,6 +620,7 @@ fn deep_copy_type_vars<'a>(
solved,
recursion_var,
unspecialized,
ambient_function,
}) => {
let new_rec_var = recursion_var.map(|var| descend_var!(var));
for variables_slice_index in solved.variables() {
@ -630,6 +631,7 @@ fn deep_copy_type_vars<'a>(
let Uls(var, _, _) = subs[uls_index];
descend_var!(var);
}
let new_ambient_function = descend_var!(ambient_function);
perform_clone!({
let new_variable_slices =
@ -657,6 +659,7 @@ fn deep_copy_type_vars<'a>(
solved: new_solved,
recursion_var: new_rec_var,
unspecialized: new_unspecialized,
ambient_function: new_ambient_function,
})
})
}

View file

@ -2317,6 +2317,8 @@ fn from_can_let<'a>(
let (_specialization_mark, (var, specialized_symbol)) =
needed_specializations.next().unwrap();
// Make sure rigid variables in the annotation are converted to flex variables.
instantiate_rigids(env.subs, def.expr_var);
// Unify the expr_var with the requested specialization once.
let _res = env.unify(var, def.expr_var);
@ -2333,6 +2335,9 @@ fn from_can_let<'a>(
_n => {
let mut stmt = rest;
// Make sure rigid variables in the annotation are converted to flex variables.
instantiate_rigids(env.subs, def.expr_var);
// Need to eat the cost and create a specialized version of the body for
// each specialization.
for (_specialization_mark, (var, specialized_symbol)) in
@ -3785,14 +3790,14 @@ pub fn with_hole<'a>(
}
ZeroArgumentTag {
variant_var,
variant_var: _,
name: tag_name,
ext_var,
closure_name,
} => {
let arena = env.arena;
let content = env.subs.get_content_without_compacting(variant_var);
let content = env.subs.get_content_without_compacting(variable);
if let Content::Structure(FlatType::Func(arg_vars, _, ret_var)) = content {
let ret_var = *ret_var;
@ -3806,7 +3811,7 @@ pub fn with_hole<'a>(
closure_name,
ext_var,
procs,
variant_var,
variable,
layout_cache,
assigned,
hole,
@ -3814,7 +3819,7 @@ pub fn with_hole<'a>(
} else {
convert_tag_union(
env,
variant_var,
variable,
assigned,
hole,
tag_name,
@ -5124,6 +5129,7 @@ fn late_resolve_ability_specialization<'a>(
solved,
unspecialized,
recursion_var: _,
ambient_function: _,
} = env.subs.get_lambda_set(*lambda_set);
debug_assert!(unspecialized.is_empty());
@ -6068,7 +6074,7 @@ fn from_can_when<'a>(
let guard_stmt = with_hole(
env,
loc_expr.value,
cond_var,
Variable::BOOL,
procs,
layout_cache,
symbol,
@ -8952,6 +8958,7 @@ where
ToLowLevelCall: Fn(ToLowLevelCallArguments<'a>) -> Call<'a> + Copy,
{
match lambda_set.runtime_representation() {
Layout::VOID => empty_lambda_set_error(),
Layout::Union(union_layout) => {
let closure_tag_id_symbol = env.unique_symbol();
@ -9111,6 +9118,11 @@ where
}
}
fn empty_lambda_set_error() -> Stmt<'static> {
let msg = "a Lambda Set is empty. Most likely there is a type error in your program.";
Stmt::RuntimeError(msg)
}
/// Use the lambda set to figure out how to make a call-by-name
#[allow(clippy::too_many_arguments)]
fn match_on_lambda_set<'a>(
@ -9125,6 +9137,7 @@ fn match_on_lambda_set<'a>(
hole: &'a Stmt<'a>,
) -> Stmt<'a> {
match lambda_set.runtime_representation() {
Layout::VOID => empty_lambda_set_error(),
Layout::Union(union_layout) => {
let closure_tag_id_symbol = env.unique_symbol();
@ -9254,9 +9267,7 @@ fn union_lambda_set_to_switch<'a>(
// there is really nothing we can do here. We generate a runtime error here which allows
// code gen to proceed. We then assume that we hit another (more descriptive) error before
// hitting this one
let msg = "a Lambda Set isempty. Most likely there is a type error in your program.";
return Stmt::RuntimeError(msg);
return empty_lambda_set_error();
}
let join_point_id = JoinPointId(env.unique_symbol());

View file

@ -1127,11 +1127,13 @@ fn resolve_lambda_set(subs: &Subs, mut var: Variable) -> ResolvedLambdaSet {
solved,
recursion_var: _,
unspecialized,
ambient_function: _,
}) => {
debug_assert!(
unspecialized.is_empty(),
"unspecialized lambda sets left over during resolution: {:?}",
"unspecialized lambda sets left over during resolution: {:?}, {:?}",
roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(var), subs),
subs.uls_of_var
);
roc_types::pretty_print::push_union(subs, solved, &mut set);
return ResolvedLambdaSet::Set(set);
@ -1966,6 +1968,7 @@ fn layout_from_lambda_set<'a>(
solved,
recursion_var,
unspecialized,
ambient_function: _,
} = lset;
if !unspecialized.is_empty() {

View file

@ -277,6 +277,7 @@ impl LambdaSet {
solved,
recursion_var: _,
unspecialized: _,
ambient_function: _,
} = lset;
// TODO: handle unspecialized
@ -700,6 +701,7 @@ impl Layout {
solved,
recursion_var,
unspecialized: _,
ambient_function: _,
} = lset;
// TODO: handle unspecialized lambda set

View file

@ -0,0 +1,699 @@
# Ambient Lambda Set Specialization
Ayaz Hafiz
# Summary
This document describes how polymorphic lambda sets are specialized and resolved
in the compiler's type solver. It's derived from the original document at https://rwx.notion.site/Ambient-Lambda-Set-Specialization-50e0208a39844ad096626f4143a6394e.
TL;DR: lambda sets are resolved by unifying their ambient arrow types in a “bottom-up” fashion.
# Background
In this section Ill explain how lambda sets and specialization lambda sets work today, mostly from the ground-up. Ill gloss over a few details and assume an understanding of type unification. The background will leave us with a direct presentation of the current specialization lambda set unification algorithm, and its limitation.
Lambda sets are a technique Roc uses for static dispatch of closures. For example,
```jsx
id1 = \x -> x
id2 = \x -> x
f = if True then id1 else id2
```
has the elaboration (solved-type annotations)
```jsx
id1 = \x -> x
^^^ id1 : a -[[id1]] -> a
id2 = \x -> x
^^^ id2 : a -[[id2]] -> a
f = if True then id1 else id2
^ f : a -[[id1, id2]] -> a
```
The syntax `-[[id1]]->` can be read as “a function that dispatches to `id1`". Then the arrow `-[[id1, id2]]->` then is “a function that dispatches to `id1`, or `id2`". The tag union `[id1, id2]` can contain payloads to represent the captures of `id1` and `id2`; however, the implications of that are out of scope for this discussion, see [Folkerts great explanation](https://github.com/rtfeldman/roc/pull/2307#discussion_r777042512) for more information. During compile-time, Roc would attach a run-time examinable tag to the value in each branch of the `f` expression body, representing whether to dispatch to `id1` or `id2`. Whenever `f` is dispatched, that tag is examined to determine exactly which function should be dispatched to. This is “**defunctionalization**”.
In the presence of [abilities](https://docs.google.com/document/d/1kUh53p1Du3fWP_jZp-sdqwb5C9DuS43YJwXHg1NzETY/edit), lambda sets get more complicated. Now, I can write something like
```jsx
Hash has hash : a -> U64 | a has Hash
zeroHash = \_ -> 0
f = if True then hash else zeroHash
```
The elaboration of `f` as `f : a -[[hash, zeroHash]]-> U64` is incorrect, in the sense that it is incomplete - `hash` is not an actual definition, and we dont know exactly what specialization of `hash` to dispatch to until the type variable `a` is resolved. This elaboration does not communicate to the code generator that the value of `hash` is actually polymorphic over `a`.
To support polymorphic values in lambda sets, we use something we call “**specialization lambda sets**”. In this technique, the lambda under the only arrow in `hash` is parameterized on (1) the type variable the `hash` specialization depends on, and (2) the “region” in the type signature of the specialization that the actual type should be recovered from.
That was a lot of words, so let me give you an example. To better illustrate how the mechanism works, lets suppose `Hash` is actually defined as `Hash has hashThunk : a -> ({} -> U64) | a has Hash`. Now lets consider the following program elaboration:
```jsx
Hash has
hashThunk : a -> ({} -> U64) | a has Hash
# ^^^^^^^^^ a -[[] + a:hashThunk:1]-> ({} -[[] + a:hashThunk:2]-> U64)
zeroHash = \_ -> \{} -> 0
#^^^^^^^ a -[[zeroHash]]-> \{} -[[lam1]]-> U64
f = if True then hash else zeroHash
#^ a -[[zeroHash] + a:hashThunk:1]-> ({} -[[lam1] + a:hashThunk:2]-> U64)
```
The grammar of a lambda set is now
```jsx
lambda_set: [[(concrete_lambda)*] (+ specialization_lambda)*]
concrete_lambda: lambda_name ( capture_type)*
specialization_lambda: <type>:ability_member_name:region
region: <number>
```
Since `hashThunk` is a specification for an ability member and not a concrete implementation, it contains only specialization lambdas, in this case parameterized over `a`, which is the type parameter that implementors of `Hash` must specialize. Since `hashThunk` has two function types, we need to distinguish how they should be resolved. For this reason we record a “region” noting where the specialization lambda occurs in an ability member signature. When `a` is resolved to a concrete type `C`, we would resolve `C:hashThunk:2` by looking up the lambda set of `C`'s specialization of `hashThunk`, at region 2.
`zeroHash` is a concrete implementation, and uses only concrete lambdas, so its two lambda sets are fully resolved with concrete types. Ive named the anonymous lambda `\{} -> 0` `lam1` for readability.
At `f`, we unify the function types in both branches. Unification of lambda sets is basically a union of both sides concrete lambdas and specialization lambdas (this is not quite true, but that doesnt matter here). This way, we preserve the fact that how `f` should be dispatched is parameterized over `a`.
Now, lets say we apply `f` to a concrete type, like
```jsx
Foo := {}
hashThunk = \@Foo {} -> \{} -> 1
#^^^^^^^^ Foo -[[Foo#hashThunk]]-> \{} -[[lam2]]-> U64
f (@Foo {})
```
The unification trace for the call `f (@Foo {})` proceeds as follows. I use `'tN`, where `N` is a number, to represent fresh unbound type variables. Since `f` is a generalized type, `a'` is the fresh type “based on `a`" created for a particular usage of `f`.
```
typeof f
~ Foo -'t1-> 't2
=>
a' -[[zeroHash] + a':hashThunk:1]-> ({} -[[lam1] + a':hashThunk:2]-> U64)
~ Foo -'t1-> 't2
=>
Foo -[[zeroHash] + Foo:hashThunk:1]-> ({} -[[lam1] + Foo:hashThunk:2]-> U64)
```
Now that the specialization lambdas type variables point to concrete types, we can resolve the concrete lambdas of `Foo:hashThunk:1` and `Foo:hashThunk:2`. Cool! Lets do that. We know that
```
hashThunk = \@Foo {} -> \{} -> 1
#^^^^^^^^ Foo -[[Foo#hashThunk]]-> \{} -[[lam2]]-> U64
```
So `Foo:hashThunk:1` is `[[Foo#hashThunk]]` and `Foo:hashThunk:2` is `[[lam2]]`. Applying that to the type of `f` we get the trace
```
Foo -[[zeroHash] + Foo:hashThunk:1]-> ({} -[[lam1] + Foo:hashThunk:2]-> U64)
<specialization time>
Foo:hashThunk:1 -> [[Foo#hashThunk]]
Foo:hashThunk:2 -> [[lam2]]
=>
Foo -[[zeroHash, Foo#hashThunk]]-> ({} -[[lam1, lam2]] -> U64)
```
Great, so now we know our options to dispatch `f` in the call `f (@Foo {})`, and the code-generator will insert tags appropriately for the specialization definition of `f` where `a = Foo` knowing the concrete lambda symbols.
# The Problem
This technique for lambda set resolution is all well and good when the specialized lambda sets are monomorphic, that is, they contain only concrete lambdas. So far in our development of the end-to-end compilation model thats been the case, and when it wasnt, theres been enough ambient information to coerce the specializations to be monomorphic.
Unfortunately we cannot assume that the specializations will be monomorphic in general, and we must now think about how to deal with that. I didnt think there was any good, efficient solution, but now we have no option other than to come up with something, so this document is a description of my attempt. But before we get there, lets whet our appetite for what the problem even is. Ive been waving my hands too long.
Lets consider the following program:
```python
F has f : a -> (b -> {}) | a has F, b has G
# ^ a -[[] + a:f:1]-> (b -[[] + a:f:2]-> {}) | a has F, b has G
G has g : b -> {} | b has G
# ^ b -[[] + b:g:1]-> {}
Fo := {}
f = \@Fo {} -> g
#^ Fo -[[Fo#f]]-> (b -[[] + b:g:1]-> {}) | b has G
# instantiation with a=Fo of
# a -[[] + a:f:1]-> (b -[[] + a:f:2]-> {}) | a has F, b has G
Go := {}
g = \@Go {} -> {}
#^ Go -[[Go#g]]-> {}
# instantiation with b=Go of
# b -[[] + b:g:1]-> {}
```
Apologies for the complicated types, I know this can be a bit confusing. It helps to look at the specialized types of `f` and `g` relative to the ability member signatures.
The key thing to notice here is that `Fo#f` must continue to vary over `b | b has G`, since it can only specialize the type parameter `a` (in this case, it specialized it to `Fo`). Its return value is the unspecialized ability member `g`, which has type `b -> {}`, as we wanted. But its lambda set **also** varies over `b`, being `b -[[] + b:g:1]-> {}`.
Suppose we have the call
```python
(f (@Fo {})) (@Go {})
```
With the present specialization technique, unification proceeds as follows:
```
== solve (f (@Fo {})) ==
typeof f
~ Fo -'t1-> 't2
a' -[[] + a':f:1]-> (b' -[[] + a':f:2]-> {})
~ Fo -'t1-> 't2
=> Fo -[[] + Fo:f:1]-> (b' -[[] + Fo:f:2]-> {})
<specialization time>
Fo:f:1 -> [[Fo#f]]
Fo:f:2 -> [[] + b'':g:1] | This is key bit 1!
=> Fo -[[Fo#f]]-> (b' -[[] + b'':g:1] -> {})
== solve (f (@Fo {})) (@Go {}) ==
return_typeof f
~ Go -'t3-> 't4
-
b' -[[] + b'':g:1] -> {} | This is key bit 2!
~ Go -'t3-> 't4 |
=> Go -[[] + b'':g:1] -> {} |
-
== final type of f ==
f : Fo -[[Fo#f]]-> (Go -[[] + b'':g:1]-> {})
```
Let's go over what happened. The important pieces are the unification traces Ive annotated as “key bits”.
In resolving `Fo:f:2`, we pulled down the let-generalized lambda set `[[] + b:g:2]` at that region in `Fo`, which means we have to generate a fresh type variable for `b` for that particular instantiation of the lambda set. That makes sense, thats how let-generalization works. So, we get the lambda set `[[] + b'':g:1]` for our particular instance.
But in key bit 2, we see that we know what we want `b''` to be! We want it to be this `b'`, which gets instantiated to `Go`. But `b'` and `b''` are independent type variables, and so unifying `b' ~ Go` doesnt solve `b'' = Go`. Instead, `b''` is now totally unbound, and in the end, we get a type for `f` that has an unspecialized lambda set, even though you or I, staring at this program, know exactly what `[[] + b'':g:1]` should really be - `[[Go#g]]`.
So where did we go wrong? Well, our problem is that we never saw that `b'` and `b''` should really be the same type variable. If only we knew that in this specialization `b'` and `b''` are the same instantiation, wed be all good.
# A Solution
Ill now explain the best way Ive thought of for us to solve this problem. If you see a better way, please let me know! Im not sure I love this solution, but I do like it a lot more than some other naive approaches.
Okay, so first well enumerate some terminology, and the exact algorithm. Then well illustrate the algorithm with some examples; my hope is this will help explain why it must proceed in the way it does. Well see that the algorithm depends on a few key invariants; Ill discuss them and their consequences along the way. Finally, well discuss a couple details regarding the algorithm not directly related to its operation, but important to recognize. I hope then, you will tell me where I have gone wrong, or where you see a better opportunity to do things.
## The algorithm
### Some definitions
- **The region invariant.** Previously we discussed the “region” of a lambda set in a specialization function definition. The way regions are assigned in the compiler follows a very specific ordering and holds a invariant well call the “region invariant”. First, lets define a procedure for creating function types and assigning regions:
```
Type = \region ->
(Type_atom, region)
| Type_function region
Type_function = \region ->
let left_type, new_region = Type (region + 1)
let right_type, new_region = Type (new_region)
let func_type = left_type -[Lambda region]-> right_type
(func_type, new_region)
```
This procedure would create functions that look like the trees(abbreviating `L=Lambda`, `a=atom` below)
```
-[L 1]->
a a
===
-[L 1]->
-[L 2]-> -[L 3]->
a a a a
===
-[L 1]->
-[L 2]-> -[L 5]->
-[L 3]-> -[L 4]-> -[L 6]-> -[L 7]->
a a a a a a a a
```
The invariant is this: for a region `r`, the only functions enclosing `r` have a region number that is less than `r`. Moreover, every region `r' < r`, either the function at `r'` encloses `r`, or is disjoint from `r`.
- **Ambient functions.** For a given lambda set at region `r`, any function that encloses `r` is called an **ambient function** of `r`. The function directly at region `r` is called the **directly ambient function**.
For example, the functions identified by `L 4`, `L 2`, and `L 1` in the last example tree above are all ambient functions of the function identified by `L 4`.
The region invariant means that the only functions that are ambient of a region `r` are those identified by regions `< r`.
- `uls_of_var`. A look aside table of the unspecialized lambda sets (uls) depending on a variable. For example, in `a -[[] + a:f:1]-> (b -[[] + a:f:2]-> {})`, there would be a mapping of `a => { [[] + a:f:1]; [[] + a:f:2] }`. When `a` gets instantiated with a concrete type, we know that these lambda sets are ready to be resolved.
### Explicit Description
The algorithm concerns what happens during the lambda-set-specialization-time. You may want to read it now, but its also helpful to first look at the intuition below, then the examples, then revisit the explicit algorithm description.
Suppose a type variable `a` with `uls_of_var` mapping `uls_a = {l1, ... ln}` has been instantiated to a concrete type `C`. Then,
1. Let each `l` in `uls_a` be of form `[concrete_lambdas + ... + C:f:r + ...]`. It has to be in this form because of how `uls_of_var` is constructed.
1. Note that there may be multiple unspecialized lambdas of form `C:f:r, C:f1:r1, ..., C:fn:rn` in `l`. In this case, let `t1, ... tm` be the other unspecialized lambdas not of form `C:_:_`, that is, none of which are now specialized to the type `C`. Then, deconstruct `l` such that `l' = [concrete_lambdas + t1 + ... + tm + C:f:r` and `l1 = [[] + C:f1:r1], ..., ln = [[] + C:fn:rn]`. Replace `l` with `l', l1, ..., ln` in `uls_a`, flattened.
2. Now, each `l` in `uls_a` has a unique unspecialized lambda of form `C:f:r`. Sort `uls_a` primarily by `f` (arbitrary order), and secondarily by `r` in descending order. This sorted list is called `uls_a'`.
1. That is, we are sorting `uls_a` so that it is partitioned by ability member name of the unspecialized lambda sets, and each partition is in descending order of region.
2. An example of the sort would be `[[] + C:foo:2], [[] + C:bar:3], [[] + C:bar:1]`.
3. For each `l` in `uls_a'` with unique unspecialized lambda `C:f:r`:
1. Let `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. Remove `C:f:r` from `t_f1`'s lambda set.
1. For example, `(b' -[[] + Fo:f:2]-> {})` if `C:f:r=Fo:f:2`. Removing `Fo:f:2`, we get `(b' -[[]]-> {})`.
2. Let `t_f2` be the directly ambient function of the specialization lambda set resolved by `C:f:r`.
1. For example, `(b -[[] + b:g:1]-> {})` if `C:f:r=Fo:f:2`, running on example from above.
3. Unify `t_f1 ~ t_f2`.
### Intuition
The intuition is that we walk up the function type being specialized, starting from the leaves. Along the way we pick up bound type variables from both the function type being specialized, and the specialization type. The region invariant makes sure we thread bound variables through an increasingly larger scope.
## Some Examples
### The motivating example
Recall the program from our problem statement
```python
F has f : a -> (b -> {}) | a has F, b has G
# ^ a -[[] + a:f:1]-> (b -[[] + a:f:2]-> {}) | a has F, b has G
G has g : b -> {} | b has G
# ^ b -[[] + b:g:1]-> {}
Fo := {}
f = \@Fo {} -> g
#^ Fo -[[Fo#f]]-> (b -[[] + b:g:1]-> {}) | b has G
# instantiation with a=Fo of
# a -[[] + a:f:1]-> (b -[[] + a:f:2]-> {}) | a has F, b has G
Go := {}
g = \@Go {} -> {}
#^ Go -[[Go#g]]-> {}
# instantiation with b=Go of
# b -[[] + b:g:1]-> {}
```
With our algorithm, the call
```python
(f (@Fo {})) (@Go {})
```
has unification proceed as follows:
```
== solve (f (@Fo {})) ==
typeof f
~ Fo -'t1-> 't2
a' -[[] + a':f:1]-> (b' -[[] + a':f:2]-> {})
~ Fo -'t1-> 't2
=> Fo -[[] + Fo:f:1]-> (b' -[[] + Fo:f:2]-> {})
<specialization time>
step 1:
uls_Fo = { [[] + Fo:f:1], [[] + Fo:f:2] }
step 2 (sort):
uls_Fo' = { [[] + Fo:f:2], [[] + Fo:f:1] }
step 3:
1. iteration: [[] + Fo:f:2]
b' -[[]]-> {} (t_f1 after removing Fo:f:2)
~ b'' -[[] + b'':g:1]-> {}
= b'' -[[] + b'':g:1]-> {}
=> typeof f now Fo -[[] + Fo:f:1]-> (b'' -[[] + b'':g:1]-> {})
2. iteration: [[] + Fo:f:1]
Fo -[[]]-> (b'' -[[] + b'':g:1]-> {}) (t_f1 after removing Fo:f:1)
~ Fo -[[Fo#f]]-> (b''' -[[] + b''':g:1]-> {})
= Fo -[[Fo#f]]-> (b''' -[[] + b''':g:1]-> {})
=> typeof f = Fo -[[Fo#f]]-> (b''' -[[] + b''':g:1]-> {})
== solve (f (@Fo {})) (@Go {}) ==
return_typeof f
~ Go -'t3-> 't4
b''' -[[] + b''':g:1]-> {}
~ Go -'t3-> 't4
=> Go -[[] + Go:g:1] -> {}
<specialization time>
step 1:
uls_Go = { [[] + Go:g:1] }
step 2 (sort):
uls_Go' = { [[] + Go:g:1] }
step 3:
1. iteration: [[] + Go:g:1]
Go -[[]]-> {} (t_f1 after removing Go:g:1)
~ Go -[[Go#g]]-> {}
= Go -[[Go#g]]-> {}
=> typeof f = Fo -[[Fo#f]]-> (Go -[[Go#g]]-> {})
== final type of f ==
f : Fo -[[Fo#f]]-> (Go -[[Go#g]]-> {})
```
There we go. Weve recovered the specialization type of the second lambda set to `Go#g`, as we wanted.
### The motivating example, in the presence of let-generalization
Suppose instead we let-generalized the motivating example, so it was a program like
```
h = f (@Fo {})
h (@Go {})
```
`h` still gets resolved correctly in this case. Its basically the same unification trace as above, except that after we find out that
```
typeof f = Fo -[[Fo#f]]-> (b''' -[[] + b''':g:1]-> {})
```
we see that `h` has type
```
b''' -[[] + b''':g:1]-> {}
```
We generalize this to
```
h : c -[[] + c:g:1]-> {}
```
Then, the call `h (@Go {})` has the trace
```
=== solve h (@Go {}) ===
typeof h
~ Go -'t1-> 't2
c' -[[] + c':g:1]-> {}
~ Go -'t1-> 't2
=> Go -[[] + Go:g:1]-> {}
<specialization time>
step 1:
uls_Go = { [[] + Go:g:1] }
step 2 (sort):
uls_Go' = { [[] + Go:g:1] }
step 3:
1. iteration: [[] + Go:g:1]
Go -[[]]-> {} (t_f1 after removing Go:g:1)
~ Go -[[Go#g]]-> {}
= Go -[[Go#g]]-> {}
=> Go -[[Go#g]]-> {}
```
### Bindings on the right side of an arrow
This continues to work if instead of a type variable being bound on the left side of an arrow, it is bound on the right side. Lets see what that looks like. Consider
```python
F has f : a -> ({} -> b) | a has F, b has G
G has g : {} -> b | b has G
Fo := {}
f = \@Fo {} -> g
#^ Fo -[[Fo#f]]-> ({} -[[] + b:g:1]-> b) | b has G
# instantiation with a=Fo of
# a -[[] + a:f:1]-> ({} -[[] + a:f:2]-> b) | a has F, b has G
Go := {}
g = \{} -> @Go {}
#^ {} -[[Go#g]]-> Go
# instantiation with b=Go of
# {} -[[] + b:g:1]-> b
```
This is symmetrical to the first example we ran through. I can include a trace if you all would like, though it could be helpful to go through yourself and see that it would work.
### Deep specializations and captures
Alright, bear with me, this is a long and contrived one, but it demonstrates how this works in the presence of polymorphic captures (its “nothing special”), and more importantly, why the bottom-up unification is important.
Heres the source program:
```python
F has f : a, b -> ({} -> ({} -> {})) | a has F, b has G
# ^ a, b -[[] + a:f:1]-> ({} -[[] + a:f:2]-> ({} -[[] + a:f:3]-> {})) | a has F, b has G
G has g : b -> ({} -> {}) | b has G
# ^ b -[[] + b:g:1]-> ({} -[[] + b:g:2]-> {}) | b has G
Fo := {}
f = \@Fo {}, b -> \{} -> g b
#^ Fo, b -[[Fo#f]]-> ({} -[[lamF b]]-> ({} -[[] + b:g:2]]-> {})) | b has G
# instantiation with a=Fo of
# a, b -[[] + a:f:1]-> ({} -[[] + a:f:2]-> ({} -[[] + a:f:3]-> {})) | a has F, b has G
Go := {}
g = \@Go {} -> \{} -> {}
#^ {} -[[Go#g]]-> ({} -[[lamG]]-> {})
# instantiation with b=Go of
# b -[[] + b:g:1]-> ({} -[[] + b:g:2]-> {}) | b has G
```
Here is the call were going to trace:
```python
(f (@Fo {}) (@Go {})) {}
```
Lets get to it.
```
=== solve (f (@Fo {}) (@Go {})) ===
typeof f
~ Fo, Go -'t1-> 't2
a, b -[[] + a:f:1]-> ({} -[[] + a:f:2]-> ({} -[[] + a:f:3]-> {}))
~ Fo, Go -'t1-> 't2
=> Fo, Go -[[] + Fo:f:1]-> ({} -[[] + Fo:f:2]-> ({} -[[] + Fo:f:3]-> {}))
<specialization time>
step 1:
uls_Fo = { [[] + Fo:f:1], [[] + Fo:f:2], [[] + Fo:f:3] }
step 2:
uls_Fo = { [[] + Fo:f:3], [[] + Fo:f:2], [[] + Fo:f:1] } (sorted)
step_3:
1. iteration: [[] + Fo:f:3]
{} -[[]]-> {} (t_f1 after removing Fo:f:3)
~ {} -[[] + b':g:2]]-> {}
= {} -[[] + b':g:2]-> {}
=> Fo, Go -[[] + Fo:f:1]-> ({} -[[] + Fo:f:2]-> ({} -[[] + b':g:2]-> {}))
2. iteration: [[] + Fo:f:2]
{} -[[]]-> ({} -[[] + b':g:2]-> {}) (t_f1 after removing Fo:f:2)
~ {} -[[lamF b'']]-> ({} -[[] + b'':g:2]]-> {})
= {} -[[lamF b'']]-> ({} -[[] + b'':g:2]]-> {})
=> Fo, Go -[[] + Fo:f:1]-> ({} -[[lamF b'']]-> ({} -[[] + b'':g:2]]-> {}))
3. iteration: [[] + Fo:f:1]
Fo, Go -[[]]-> ({} -[[lamF b'']]-> ({} -[[] + b'':g:2]]-> {})) (t_f1 after removing Fo:f:2)
~ Fo, b''' -[[Fo#f]]-> ({} -[[lamF b''']]-> ({} -[[] + b''':g:2]]-> {}))
= Fo, Go -[[Fo#f]]-> ({} -[[lamF Go]]-> ({} -[[] + Go:g:2]-> {}))
<specialization time>
step 1:
uls_Go = { [[] + Go:g:2] }
step 2:
uls_Go = { [[] + Go:g:2] } (sorted)
step_3:
1. iteration: [[] + Go:g:2]
{} -[[]]-> {} (t_f1 after removing Go:g:2)
~ {} -[[lamG]]-> {}
= {} -[[lamG]]-> {}
=> Fo, Go -[[Fo#f]]-> ({} -[[lamF Go]]-> ({} -[[lamG]]-> {}))
== final type of f ==
f : Fo, Go -[[Fo#f]]-> ({} -[[lamF Go]]-> ({} -[[lamG]]-> {}))
```
Look at that! Resolved the capture, and all the lambdas.
Notice that in the first `<specialization time>` trace, had we not sorted the `Fo:f:_` specialization lambdas in descending order of region, we would have resolved `Fo:f:3` last, and not bound the specialized `[[] + b':g:2]` to any `b'` variable. Intuitively, thats because the variable we need to bind it to occurs in the most ambient function type of all those specialization lambdas: the one at `[[] + Fo:f:1]`
## An important requirement
There is one invariant I have left implicit in this construction, that may not hold in general. (Maybe I left others that you noticed that dont hold - let me know!). That invariant is that any type variable in a signature is bound in either the left or right hand side of an arrow.
I know what youre thinking, “of course, how else can you get a type variable?” Well, they have played us for fools. Evil lies in the midst. No sanctity passes unscathed through ad-hoc polymorphism.
```python
Evil has
getEvil : {} -> a | a has Evil
eatEvil : a -> ({} -> {}) | a has Evil
f = eatEvil (getEvil {})
```
The type of `f` here is `{} -> [[] + a:eatEvil:2]-> {} | a has Evil`. “Blasphemy!” you cry. Well, youre totally right, this program is total nonsense. Somehow its well-typed, but the code generator cant just synthesize an `a | a has Evil` out of nowhere.
Well, okay, the solution is actually pretty simple - make this a type error. Its actually a more general problem with abilities, for example we can type the following program:
```python
Evil has
getEvil : {} -> a | a has Evil
eatEvil : a -> {} | a has Evil
f = eatEvil (getEvil {})
```
Now the type variable `a | a has Evil` isnt even visible on the surface: `f` has type `f : {}`. But it lies in the middle, snuggly between `getEvil` and `eatEvil` where it cant be seen.
In fact, to us, detecting these cases is straightforward - such nonsense programs are identified when they have type variables that dont escape to either the front or the back of an exposed type. Thats the only way to do monomorphization - otherwise, we could have values that are pathologically polymorphic, which means they are either unused, or this kind of non-codegen-able case.
How do we make this a type error? A couple options have been considered, but we havent settled on anything.
1. One approach, suggested by Richard, is to sort abilities into strongly-connected components and see if there is any zig-zag chain of member signatures in a SCC where an ability-bound type variable doesnt escape through the front or back. We can observe two things: (1) such SCCs can only exist within a single module because Roc doesnt have (source-level) circular dependencies and (2) we only need to examine pairs of functions have at least one type variable only appearing on one side of an arrow. That means the worst case performance of this analysis is quadratic in the number of ability members in a module. The downside of this approach is that it would reject some uses of abilities that can be resolved and code-generated by the compiler.
2. Another approach is to check whether generalized variables in a let-bound definitions body escaped out the front or back of the let-generalized definitions type (and **not** in a lambda set, for the reasons described above). This admits some programs that would be illegal with the other analysis but cant be performed until typechecking. As for performance, note that new unbound type variables in a body can only be introduced by using a let-generalized symbol that is polymorphic. Those variables would need to be checked, so the performance of this approach on a per-module basis is linear in the number of let-generalized symbols used in the module (assuming the number of generalized variables returned is a constant factor).
## A Property thats lost, and how we can hold on to it
One question I asked myself was, does this still ensure lambda sets can vary over multiple able type parameters? At first, I believed the answer was yes — however, this may not hold and be sound. For example, consider
```python
J has j : j -> (k -> {}) | j has J, k has K
K has k : k -> {} | k has K
C := {}
j = \@C _ -> k
D := {}
j = \@D _ -> k
E := {}
k = \@E _ -> {}
f = \flag, a, b, c ->
it = when flag is
A -> j a
B -> j b
it c
```
The first branch has type (`a` has generalized type `a'`)
```
c'' -[[] + a':j:2]-> {}
```
The second branch has type (`b` has generalized type `b'`)
```
c''' -[[] + b':j:2]-> {}
```
So now, how do we unify this? Well, following the construction above, we must unify `a'` and `b'` - but this demands that they are actually the same type variable. Is there another option?
Well, one idea is that during normal type unification, we simply take the union of unspecialized lambda sets with **disjoint** variables. In the case above, we would get `c' -[[] + a':j:2 + b':j:2]` (supposing `c` has type `c'`). During lambda set compaction, when we unify ambient types, choose one non-concrete type to unify with. Since were maintaining the invariant that each generalized type variable appears at least once on one side of an arrow, eventually you will have picked up all type variables in unspecialized lambda sets.
```
=== monomorphize (f A (@C {}) (@D {}) (@E {})) ===
(inside f, solving `it`:)
it ~ E -[[] + C:j:2 + D:j:2]-> {}
<specialization time: C>
step 1:
uls_C = { [[] + C:j:2 + D:j:2] }
step 2:
uls_C = { [[] + C:j:2 + D:j:2] } (sorted)
step_3:
1. iteration: [[] + C:j:2 + D:j:2]
E -[[] + D:j:2]-> {} (t_f1 after removing C:j:2)
~ k' -[[] + k':k:2]-> {}
= E -[[] + E:k:2 + D:j:2]-> {} (no non-concrete type to unify with)
=> E -[[] + E:k:2 + D:j:2]-> {}
<specialization time: D>
step 1:
uls_D = { [[] + E:k:2 + D:j:2] }
step 2:
uls_D = { [[] + E:k:2 + D:j:2] } (sorted)
step_3:
1. iteration: [[] + E:k:2 + D:j:2]
E -[[] + E:k:2]-> {} (t_f1 after removing D:j:2)
~ k'' -[[] + k'':k:2]-> {}
= E -[[] + E:k:2 + E:k:2]-> {} (no non-concrete type to unify with)
=> E -[[] + E:k:2 + E:k:2]-> {}
<specialization time: E>
step 1:
uls_E = { [[] + E:k:2], [[] + E:k:2] }
step 2:
uls_E = { [[] + E:k:2], [[] + E:k:2] } (sorted)
step_3:
1. iteration: [[] + E:k:2]
E -[[]]-> {} (t_f1 after removing E:k:2)
~ E -[[lamE]]-> {}
= E -[[lamE]]-> {}
=> E -[[lamE]]-> {}
=> E -[[lamE]]-> {}
== final type of it ==
it : E -[[lamE]]-> {}
```
The disjointedness is important - we want to unify unspecialized lambdas whose type variables are equivalent. For example,
```
f = \flag, a, c ->
it = when flag is
A -> j a
B -> j a
it c
```
Should produce `it` having generalized type
```
c' -[[] + a':j:2]-> {}
```
and not
```
c' -[[] + a':j:2 + a':j:2]-> {}
```
For now, we will not try to preserve this property, and instead unify all type variables with the same member/region in a lambda set. We can improve the status of this over time.
# Conclusion
Will this work? I think so, but I dont know. In the sense that, I am sure it will work for some of the problems we are dealing with today, but there may be even more interactions that arent clear to us until further down the road.
Obviously, this is not a rigorous study of this problem. We are making several assumptions, and I have not proved any of the properties I claim. However, the intuition makes sense to me, predicated on the “type variables escape either the front or back of a type” invariant, and this is the only approach that really makes sense to me while only being a little bit complicated. Let me know what you think.
# Appendix
## Optimization: only the lowest-region ambient function type is needed
You may have observed that step 1 and step 2 of the algorithm are somewhat overkill, really, it seems you only need the lowest-number regions directly ambient function type to unify the specialization with. Thats because by the region invariant, the lowest-regions ambient function would contain every other regions ambient function.
This optimization is correct with a change to the region numbering scheme:
```python
Type = \region ->
(Type_atom, region)
| Type_function region
Type_function = \region ->
let left_type = Type (region * 2)
let right_type = Type (region * 2 + 1)
let func_type = left_type -[Lambda region]-> right_type
func_type
```
Which produces a tree like
```
-[L 1]->
-[L 2]-> -[L 3]->
-[L 4]-> -[L 5]-> -[L 6]-> -[L 7]->
a a a a a a a a
```
Now, given a set of `uls` sorted in increasing order of region, you can remove all `uls` that have region `r` such that a floored 2-divisor of `r` is another region `r'` of a unspecialized lambda in `uls`. For example, given `[a:f:2, a:f:5, a:f3, a:f:7]`, you only need to keep `[a:f:2, a:f:3]`.
Then, when running the algorithm, you must remove unspecialized lambdas of form `C:f:_` from **all** nested lambda sets in the directly ambient function, not just in the directly ambient function. This will still be cheaper than unifying deeper lambda sets, but may be an inconvenience.
## Testing Strategies
- Quickcheck - the shape of functions we care about is quite clearly defined. Basically just create a bunch of let-bound functions, polymorphic over able variables, use them in an expression that evaluates monomorphically, and check that everything in the monomorphic expression is resolved.

View file

@ -6,9 +6,10 @@ use roc_can::module::RigidVariables;
use roc_collections::all::MutMap;
use roc_collections::VecMap;
use roc_derive_key::GlobalDerivedSymbols;
use roc_error_macros::internal_error;
use roc_module::symbol::Symbol;
use roc_types::solved_types::Solved;
use roc_types::subs::{ExposedTypesStorageSubs, StorageSubs, Subs, Variable};
use roc_types::subs::{Content, ExposedTypesStorageSubs, FlatType, StorageSubs, Subs, Variable};
use roc_types::types::Alias;
#[derive(Debug)]
@ -100,10 +101,29 @@ pub fn exposed_types_storage_subs(
for (_, member_specialization) in solved_specializations.iter() {
for (_, &specialization_lset_var) in member_specialization.specialization_lambda_sets.iter()
{
let new_var = storage_subs
.import_variable_from(subs, specialization_lset_var)
let specialization_lset_ambient_function_var = subs
.get_lambda_set(specialization_lset_var)
.ambient_function;
// Import the ambient function of this specialization lambda set; that will import the
// lambda set as well. The ambient function is needed for the lambda set compaction
// algorithm.
let imported_lset_ambient_function_var = storage_subs
.import_variable_from(subs, specialization_lset_ambient_function_var)
.variable;
stored_specialization_lambda_set_vars.insert(specialization_lset_var, new_var);
let imported_lset_var = match storage_subs
.as_inner()
.get_content_without_compacting(imported_lset_ambient_function_var)
{
Content::Structure(FlatType::Func(_, lambda_set_var, _)) => *lambda_set_var,
content => internal_error!(
"ambient lambda set function import is not a function, found: {:?}",
roc_types::subs::SubsFmtContent(content, storage_subs.as_inner())
),
};
stored_specialization_lambda_set_vars
.insert(specialization_lset_var, imported_lset_var);
}
}

File diff suppressed because it is too large Load diff

View file

@ -4024,7 +4024,8 @@ mod solve_expr {
{ x, y }
"#
),
"{ x : I64, y ? Bool }* -> { x : I64, y : Bool }",
// TODO: when structural types unify with alias, they should take the alias name
"{ x : I64, y ? [False, True] }* -> { x : I64, y : Bool }",
);
}
@ -6484,6 +6485,7 @@ mod solve_expr {
}
#[test]
#[ignore = "TODO: fix unification of derived types"]
fn encode_record() {
infer_queries!(
indoc!(
@ -6503,6 +6505,7 @@ mod solve_expr {
}
#[test]
#[ignore = "TODO: fix unification of derived types"]
fn encode_record_with_nested_custom_impl() {
infer_queries!(
indoc!(
@ -7066,7 +7069,7 @@ mod solve_expr {
"#
),
"F U8 Str",
);
)
}
#[test]
@ -7083,7 +7086,7 @@ mod solve_expr {
"#
),
"F U8 Str -> F U8 Str",
);
)
}
#[test]
@ -7099,6 +7102,188 @@ mod solve_expr {
"#
),
"F * {}* -> F * Str",
)
}
#[test]
fn polymorphic_lambda_set_specialization() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
F has f : a -> (b -> {}) | a has F, b has G
G has g : b -> {} | b has G
Fo := {}
f = \@Fo {} -> g
#^{-1}
Go := {}
g = \@Go {} -> {}
#^{-1}
main = (f (@Fo {})) (@Go {})
# ^
# ^^^^^^^^^^
"#
),
&[
"Fo#f(10) : Fo -[[f(10)]]-> (b -[[] + b:g(8):1]-> {}) | b has G",
"Go#g(11) : Go -[[g(11)]]-> {}",
"Fo#f(10) : Fo -[[f(10)]]-> (Go -[[g(11)]]-> {})",
"f (@Fo {}) : Go -[[g(11)]]-> {}",
],
);
}
#[test]
fn polymorphic_lambda_set_specialization_bound_output() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
F has f : a -> ({} -> b) | a has F, b has G
G has g : {} -> b | b has G
Fo := {}
f = \@Fo {} -> g
#^{-1}
Go := {}
g = \{} -> @Go {}
#^{-1}
main =
foo = 1
@Go it = (f (@Fo {})) {}
# ^
# ^^^^^^^^^^
{foo, it}
"#
),
&[
"Fo#f(10) : Fo -[[f(10)]]-> ({} -[[] + b:g(8):1]-> b) | b has G",
"Go#g(11) : {} -[[g(11)]]-> Go",
"Fo#f(10) : Fo -[[f(10)]]-> ({} -[[g(11)]]-> Go)",
"f (@Fo {}) : {} -[[g(11)]]-> Go",
],
);
}
#[test]
fn polymorphic_lambda_set_specialization_with_let_generalization() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
F has f : a -> (b -> {}) | a has F, b has G
G has g : b -> {} | b has G
Fo := {}
f = \@Fo {} -> g
#^{-1}
Go := {}
g = \@Go {} -> {}
#^{-1}
main =
h = f (@Fo {})
# ^ ^
h (@Go {})
# ^
"#
),
&[
"Fo#f(10) : Fo -[[f(10)]]-> (b -[[] + b:g(8):1]-> {}) | b has G",
"Go#g(11) : Go -[[g(11)]]-> {}",
// TODO SERIOUS: Let generalization is broken here, and this is NOT correct!!
// Two problems:
// - 1. `{}` always has its rank adjusted to the toplevel, which forces the rest
// of the type to the toplevel, but that is NOT correct here!
// - 2. During solving lambda set compaction cannot happen until an entire module
// is solved, which forces resolved-but-not-yet-compacted lambdas in
// unspecialized lambda sets to pull the rank into a lower, non-generalized
// rank. Special-casing for that is a TERRIBLE HACK that interferes very
// poorly with (1)
//
// We are BLOCKED on https://github.com/rtfeldman/roc/issues/3207 to make this work
// correctly!
// See also https://github.com/rtfeldman/roc/pull/3175, a separate, but similar problem.
"h : Go -[[g(11)]]-> {}",
"Fo#f(10) : Fo -[[f(10)]]-> (Go -[[g(11)]]-> {})",
"h : Go -[[g(11)]]-> {}",
],
);
}
#[test]
fn polymorphic_lambda_set_specialization_with_let_generalization_unapplied() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
F has f : a -> (b -> {}) | a has F, b has G
G has g : b -> {} | b has G
Fo := {}
f = \@Fo {} -> g
#^{-1}
Go := {}
g = \@Go {} -> {}
#^{-1}
main =
#^^^^{-1}
h = f (@Fo {})
# ^ ^
h
"#
),
&[
"Fo#f(10) : Fo -[[f(10)]]-> (b -[[] + b:g(8):1]-> {}) | b has G",
"Go#g(11) : Go -[[g(11)]]-> {}",
"main : b -[[] + b:g(8):1]-> {} | b has G",
"h : b -[[] + b:g(8):1]-> {} | b has G",
"Fo#f(10) : Fo -[[f(10)]]-> (b -[[] + b:g(8):1]-> {}) | b has G",
],
);
}
#[test]
fn polymorphic_lambda_set_specialization_with_deep_specialization_and_capture() {
infer_queries!(
indoc!(
r#"
app "test" provides [main] to "./platform"
F has f : a, b -> ({} -> ({} -> {})) | a has F, b has G
G has g : b -> ({} -> {}) | b has G
Fo := {}
f = \@Fo {}, b -> \{} -> g b
#^{-1}
Go := {}
g = \@Go {} -> \{} -> {}
#^{-1}
main =
(f (@Fo {}) (@Go {})) {}
# ^
"#
),
&[
"Fo#f(10) : Fo, b -[[f(10)]]-> ({} -[[13(13) b]]-> ({} -[[] + b:g(8):2]-> {})) | b has G",
"Go#g(11) : Go -[[g(11)]]-> ({} -[[14(14)]]-> {})",
"Fo#f(10) : Fo, Go -[[f(10)]]-> ({} -[[13(13) Go]]-> ({} -[[14(14)]]-> {}))",
],
);
}

View file

@ -594,6 +594,7 @@ fn one_field_record() {
}
#[test]
#[ignore = "TODO #3421 unification of unspecialized variables in lambda sets currently causes this to be derived incorrectly"]
fn two_field_record() {
derive_test(v!({ a: v!(U8), b: v!(STR), }), |golden| {
assert_snapshot!(golden, @r###"
@ -649,6 +650,7 @@ fn tag_one_label_zero_args() {
}
#[test]
#[ignore = "TODO #3421 unification of unspecialized variables in lambda sets currently causes this to be derived incorrectly"]
fn tag_one_label_two_args() {
derive_test(v!([A v!(U8) v!(STR)]), |golden| {
assert_snapshot!(golden, @r###"
@ -673,6 +675,7 @@ fn tag_one_label_two_args() {
}
#[test]
#[ignore = "TODO #3421 unification of unspecialized variables in lambda sets currently causes this to be derived incorrectly"]
fn tag_two_labels() {
derive_test(v!([A v!(U8) v!(STR) v!(U16), B v!(STR)]), |golden| {
assert_snapshot!(golden, @r###"
@ -700,6 +703,7 @@ fn tag_two_labels() {
}
#[test]
#[ignore = "TODO #3421 unification of unspecialized variables in lambda sets currently causes this to be derived incorrectly"]
fn recursive_tag_union() {
derive_test(v!([Nil, Cons v!(U8) v!(*lst) ] as lst), |golden| {
assert_snapshot!(golden, @r###"

View file

@ -350,3 +350,28 @@ fn encode_use_stdlib() {
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm"))]
fn encode_use_stdlib_without_wrapping_custom() {
assert_evals_to!(
indoc!(
r#"
app "test"
imports [Encode.{ toEncoder }, Json]
provides [main] to "./platform"
HelloWorld := {}
toEncoder = \@HelloWorld {} -> Encode.string "Hello, World!\n"
main =
result = Str.fromUtf8 (Encode.toBytes (@HelloWorld {}) Json.format)
when result is
Ok s -> s
_ -> "<bad>"
"#
),
RocStr::from("\"Hello, World!\n\""),
RocStr
)
}

View file

@ -268,3 +268,65 @@ fn issue_2583_specialize_errors_behind_unified_branches() {
RocResult<i64, bool>
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn roc_result_after_on_ok() {
assert_evals_to!(indoc!(
r#"
input : Result I64 Str
input = Ok 1
Result.after input \num ->
if num < 0 then Err "negative!" else Ok -num
"#),
RocResult::ok(-1),
RocResult<i64, RocStr>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn roc_result_after_on_err() {
assert_evals_to!(indoc!(
r#"
input : Result I64 Str
input = (Err "already a string")
Result.after input \num ->
if num < 0 then Err "negative!" else Ok -num
"#),
RocResult::err(RocStr::from("already a string")),
RocResult<i64, RocStr>
);
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn roc_result_after_err() {
assert_evals_to!(indoc!(
r#"
result : Result Str I64
result =
Result.afterErr (Ok "already a string") \num ->
if num < 0 then Ok "negative!" else Err -num
result
"#),
RocResult::ok(RocStr::from("already a string")),
RocResult<RocStr, i64>
);
assert_evals_to!(indoc!(
r#"
result : Result Str I64
result =
Result.afterErr (Err 100) \num ->
if num < 0 then Ok "negative!" else Err -num
result
"#),
RocResult::err(-100),
RocResult<RocStr, i64>
);
}

View file

@ -1727,3 +1727,89 @@ fn issue_3261_non_nullable_unwrapped_recursive_union_at_index() {
RocStr
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn instantiate_annotated_as_recursive_alias_toplevel() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Value : [Nil, Array (List Value)]
foo : [Nil]*
foo = Nil
it : Value
it = foo
main =
when it is
Nil -> 123i64
_ -> -1i64
"#
),
123,
i64
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn instantiate_annotated_as_recursive_alias_polymorphic_expr() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
Value : [Nil, Array (List Value)]
foo : [Nil]*
foo = Nil
it : Value
it = foo
when it is
Nil -> 123i64
_ -> -1i64
"#
),
123,
i64
)
}
#[test]
#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))]
fn instantiate_annotated_as_recursive_alias_multiple_polymorphic_expr() {
assert_evals_to!(
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
Value : [Nil, Array (List Value)]
foo : [Nil]*
foo = Nil
v1 : Value
v1 = foo
Value2 : [Nil, B U16, Array (List Value)]
v2 : Value2
v2 = foo
when {v1, v2} is
{v1: Nil, v2: Nil} -> 123i64
{v1: _, v2: _} -> -1i64
"#
),
123,
i64
)
}

View file

@ -95,8 +95,10 @@ impl<T: FromWasmerMemory + Clone> FromWasmerMemory for RocList<T> {
}
}
impl<T: FromWasmerMemory + Wasm32Sized, E: FromWasmerMemory + Wasm32Sized> FromWasmerMemory
for RocResult<T, E>
impl<T, E> FromWasmerMemory for RocResult<T, E>
where
T: FromWasmerMemory + Wasm32Sized,
E: FromWasmerMemory + Wasm32Sized,
{
fn decode(memory: &wasmer::Memory, offset: u32) -> Self {
let tag_offset = Ord::max(T::ACTUAL_WIDTH, E::ACTUAL_WIDTH);

View file

@ -1,18 +1,14 @@
procedure List.5 (#Attr.2, #Attr.3):
let List.283 : List {} = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.2 #Attr.3;
let List.295 : List {} = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.2 #Attr.3;
decref #Attr.2;
ret List.283;
ret List.295;
procedure Test.2 (Test.3):
let Test.7 : {} = Struct {};
let Test.6 : {} = CallByName Test.8 Test.7;
ret Test.6;
procedure Test.8 (Test.9):
Error The `#UserApp.IdentId(8)` function could not be generated, likely due to a type error.
Error a Lambda Set is empty. Most likely there is a type error in your program.
procedure Test.0 ():
let Test.1 : List {} = Array [];
let Test.1 : List [] = Array [];
let Test.5 : {} = Struct {};
let Test.4 : List {} = CallByName List.5 Test.1 Test.5;
ret Test.4;

View file

@ -1,6 +1,6 @@
procedure List.5 (#Attr.2, #Attr.3):
Error UnresolvedTypeVar crates/compiler/mono/src/ir.rs line 5030
Error UnresolvedTypeVar crates/compiler/mono/src/ir.rs line 5035
procedure Test.0 ():
let Test.1 : List {} = Array [];
Error UnresolvedTypeVar crates/compiler/mono/src/ir.rs line 4557
let Test.1 : List [] = Array [];
Error UnresolvedTypeVar crates/compiler/mono/src/ir.rs line 4562

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.189 : I128 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.189;
let Num.258 : I128 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.258;
procedure Test.0 ():
let Test.6 : I128 = 18446744073709551616i64;

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : U128 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : U128 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.0 ():
let Test.2 : U128 = 170141183460469231731687303715884105728u128;

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.0 ():
let Test.2 : U64 = 9999999999999999999i64;

View file

@ -1,6 +1,6 @@
procedure List.6 (#Attr.2):
let List.283 : U64 = lowlevel ListLen #Attr.2;
ret List.283;
let List.295 : U64 = lowlevel ListLen #Attr.2;
ret List.295;
procedure Test.1 (Test.5):
let Test.2 : I64 = 41i64;

View file

@ -1,11 +1,11 @@
procedure Dict.1 ():
let Dict.16 : Dict [] [] = lowlevel DictEmpty ;
ret Dict.16;
let Dict.25 : Dict [] [] = lowlevel DictEmpty ;
ret Dict.25;
procedure Dict.7 (#Attr.2):
let Dict.15 : U64 = lowlevel DictSize #Attr.2;
let Dict.24 : U64 = lowlevel DictSize #Attr.2;
dec #Attr.2;
ret Dict.15;
ret Dict.24;
procedure Test.0 ():
let Test.2 : Dict [] [] = CallByName Dict.1;

View file

@ -1,26 +1,26 @@
procedure List.2 (List.77, List.78):
let List.288 : U64 = CallByName List.6 List.77;
let List.285 : Int1 = CallByName Num.22 List.78 List.288;
if List.285 then
let List.287 : {} = CallByName List.60 List.77 List.78;
let List.286 : [C {}, C {}] = TagId(1) List.287;
ret List.286;
procedure List.2 (List.78, List.79):
let List.300 : U64 = CallByName List.6 List.78;
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
if List.297 then
let List.299 : {} = CallByName List.60 List.78 List.79;
let List.298 : [C {}, C {}] = TagId(1) List.299;
ret List.298;
else
let List.284 : {} = Struct {};
let List.283 : [C {}, C {}] = TagId(0) List.284;
ret List.283;
let List.296 : {} = Struct {};
let List.295 : [C {}, C {}] = TagId(0) List.296;
ret List.295;
procedure List.6 (#Attr.2):
let List.290 : U64 = lowlevel ListLen #Attr.2;
ret List.290;
let List.302 : U64 = lowlevel ListLen #Attr.2;
ret List.302;
procedure List.60 (#Attr.2, #Attr.3):
let List.289 : {} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.289;
let List.301 : {} = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.301;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.188 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.257;
procedure Test.2 (Test.6):
let Test.18 : Str = "bar";

View file

@ -1,16 +1,16 @@
procedure List.4 (List.88, List.89):
let List.285 : U64 = 1i64;
let List.284 : List U8 = CallByName List.65 List.88 List.285;
let List.283 : List U8 = CallByName List.66 List.284 List.89;
ret List.283;
procedure List.4 (List.89, List.90):
let List.297 : U64 = 1i64;
let List.296 : List U8 = CallByName List.65 List.89 List.297;
let List.295 : List U8 = CallByName List.66 List.296 List.90;
ret List.295;
procedure List.65 (#Attr.2, #Attr.3):
let List.287 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.287;
let List.299 : List U8 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.299;
procedure List.66 (#Attr.2, #Attr.3):
let List.286 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.286;
let List.298 : List U8 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.298;
procedure Test.20 (Test.22):
let Test.34 : {U8} = Struct {Test.22};

View file

@ -1,10 +1,10 @@
procedure Num.20 (#Attr.2, #Attr.3):
let Num.189 : I64 = lowlevel NumSub #Attr.2 #Attr.3;
ret Num.189;
let Num.258 : I64 = lowlevel NumSub #Attr.2 #Attr.3;
ret Num.258;
procedure Num.21 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumMul #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumMul #Attr.2 #Attr.3;
ret Num.257;
procedure Test.1 (Test.15, Test.16):
joinpoint Test.7 Test.2 Test.3:

View file

@ -0,0 +1,5 @@
procedure Test.0 ():
let Test.9 : [<rnu><null>, C List *self] = TagId(1) ;
let Test.10 : [C List [<rnu><null>, C List *self], C U16, C ] = TagId(2) ;
let Test.12 : {[<rnu><null>, C List *self], [C List [<rnu><null>, C List *self], C U16, C ]} = Struct {Test.9, Test.10};
ret Test.12;

View file

@ -0,0 +1,3 @@
procedure Test.0 ():
let Test.5 : [<rnu><null>, C List *self] = TagId(1) ;
ret Test.5;

View file

@ -0,0 +1,7 @@
procedure Test.4 ():
let Test.7 : [<rnu><null>, C List *self] = TagId(1) ;
ret Test.7;
procedure Test.0 ():
let Test.6 : [<rnu><null>, C List *self] = CallByName Test.4;
ret Test.6;

View file

@ -1,10 +1,10 @@
procedure List.6 (#Attr.2):
let List.283 : U64 = lowlevel ListLen #Attr.2;
ret List.283;
let List.295 : U64 = lowlevel ListLen #Attr.2;
ret List.295;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.190 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.190;
let Num.259 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.259;
procedure Test.0 ():
let Test.1 : List I64 = Array [1i64, 2i64];

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.0 ():
let Test.2 : I64 = 1i64;

View file

@ -1,6 +1,6 @@
procedure Num.45 (#Attr.2):
let Num.188 : I64 = lowlevel NumRound #Attr.2;
ret Num.188;
let Num.257 : I64 = lowlevel NumRound #Attr.2;
ret Num.257;
procedure Test.0 ():
let Test.2 : Float64 = 3.6f64;

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.0 ():
let Test.1 : I64 = 3i64;

View file

@ -1,14 +1,22 @@
procedure Num.40 (#Attr.2, #Attr.3):
let Num.193 : I64 = 0i64;
let Num.190 : Int1 = lowlevel NotEq #Attr.3 Num.193;
if Num.190 then
let Num.192 : I64 = lowlevel NumDivUnchecked #Attr.2 #Attr.3;
let Num.191 : [C {}, C I64] = TagId(1) Num.192;
ret Num.191;
procedure Bool.7 (#Attr.2, #Attr.3):
let Bool.9 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
ret Bool.9;
procedure Num.39 (#Attr.2, #Attr.3):
let Num.263 : I64 = lowlevel NumDivUnchecked #Attr.2 #Attr.3;
ret Num.263;
procedure Num.40 (Num.229, Num.230):
let Num.262 : I64 = 0i64;
let Num.259 : Int1 = CallByName Bool.7 Num.230 Num.262;
if Num.259 then
let Num.261 : {} = Struct {};
let Num.260 : [C {}, C I64] = TagId(0) Num.261;
ret Num.260;
else
let Num.189 : {} = Struct {};
let Num.188 : [C {}, C I64] = TagId(0) Num.189;
ret Num.188;
let Num.258 : I64 = CallByName Num.39 Num.229 Num.230;
let Num.257 : [C {}, C I64] = TagId(1) Num.258;
ret Num.257;
procedure Test.0 ():
let Test.8 : I64 = 1000i64;

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.0 ():
let Test.10 : I64 = 41i64;

View file

@ -1,55 +1,67 @@
procedure List.2 (List.77, List.78):
let List.297 : U64 = CallByName List.6 List.77;
let List.293 : Int1 = CallByName Num.22 List.78 List.297;
if List.293 then
let List.295 : I64 = CallByName List.60 List.77 List.78;
let List.294 : [C {}, C I64] = TagId(1) List.295;
ret List.294;
procedure Bool.7 (#Attr.2, #Attr.3):
let Bool.9 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
ret Bool.9;
procedure List.2 (List.78, List.79):
let List.309 : U64 = CallByName List.6 List.78;
let List.305 : Int1 = CallByName Num.22 List.79 List.309;
if List.305 then
let List.307 : I64 = CallByName List.60 List.78 List.79;
let List.306 : [C {}, C I64] = TagId(1) List.307;
ret List.306;
else
let List.292 : {} = Struct {};
let List.291 : [C {}, C I64] = TagId(0) List.292;
ret List.291;
let List.304 : {} = Struct {};
let List.303 : [C {}, C I64] = TagId(0) List.304;
ret List.303;
procedure List.6 (#Attr.2):
let List.298 : U64 = lowlevel ListLen #Attr.2;
ret List.298;
let List.310 : U64 = lowlevel ListLen #Attr.2;
ret List.310;
procedure List.60 (#Attr.2, #Attr.3):
let List.296 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.296;
let List.308 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.308;
procedure List.9 (List.205):
let List.290 : U64 = 0i64;
let List.283 : [C {}, C I64] = CallByName List.2 List.205 List.290;
let List.287 : U8 = 1i64;
let List.288 : U8 = GetTagId List.283;
let List.289 : Int1 = lowlevel Eq List.287 List.288;
if List.289 then
let List.206 : I64 = UnionAtIndex (Id 1) (Index 0) List.283;
let List.284 : [C Int1, C I64] = TagId(1) List.206;
ret List.284;
procedure List.9 (List.206):
let List.302 : U64 = 0i64;
let List.295 : [C {}, C I64] = CallByName List.2 List.206 List.302;
let List.299 : U8 = 1i64;
let List.300 : U8 = GetTagId List.295;
let List.301 : Int1 = lowlevel Eq List.299 List.300;
if List.301 then
let List.207 : I64 = UnionAtIndex (Id 1) (Index 0) List.295;
let List.296 : [C Int1, C I64] = TagId(1) List.207;
ret List.296;
else
let List.286 : Int1 = true;
let List.285 : [C Int1, C I64] = TagId(0) List.286;
ret List.285;
let List.298 : Int1 = true;
let List.297 : [C Int1, C I64] = TagId(0) List.298;
ret List.297;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.188 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.257;
procedure Str.27 (#Attr.2):
let #Attr.3 : {I64, U8} = lowlevel StrToNum #Attr.2;
let Str.159 : U8 = StructAtIndex 1 #Attr.3;
let Str.160 : U8 = 0i64;
let Str.156 : Int1 = lowlevel NumGt Str.159 Str.160;
if Str.156 then
let Str.158 : Int1 = false;
let Str.157 : [C Int1, C I64] = TagId(0) Str.158;
ret Str.157;
procedure Str.27 (Str.88):
let Str.194 : [C Int1, C I64] = CallByName Str.67 Str.88;
ret Str.194;
procedure Str.47 (#Attr.2):
let Str.202 : {I64, U8} = lowlevel StrToNum #Attr.2;
ret Str.202;
procedure Str.67 (Str.189):
let Str.190 : {I64, U8} = CallByName Str.47 Str.189;
let Str.200 : U8 = StructAtIndex 1 Str.190;
let Str.201 : U8 = 0i64;
let Str.197 : Int1 = CallByName Bool.7 Str.200 Str.201;
if Str.197 then
let Str.199 : I64 = StructAtIndex 0 Str.190;
let Str.198 : [C Int1, C I64] = TagId(1) Str.199;
ret Str.198;
else
let Str.155 : I64 = StructAtIndex 0 #Attr.3;
let Str.154 : [C Int1, C I64] = TagId(1) Str.155;
ret Str.154;
let Str.196 : Int1 = false;
let Str.195 : [C Int1, C I64] = TagId(0) Str.196;
ret Str.195;
procedure Test.0 ():
let Test.4 : Int1 = true;

View file

@ -1,10 +1,10 @@
procedure Num.94 (#Attr.2):
let Num.188 : Str = lowlevel NumToStr #Attr.2;
ret Num.188;
let Num.257 : Str = lowlevel NumToStr #Attr.2;
ret Num.257;
procedure Num.94 (#Attr.2):
let Num.189 : Str = lowlevel NumToStr #Attr.2;
ret Num.189;
let Num.258 : Str = lowlevel NumToStr #Attr.2;
ret Num.258;
procedure Test.1 (Test.4):
let Test.16 : [C U8, C U64] = TagId(1) Test.4;

View file

@ -1,16 +1,16 @@
procedure List.4 (List.88, List.89):
let List.285 : U64 = 1i64;
let List.284 : List I64 = CallByName List.65 List.88 List.285;
let List.283 : List I64 = CallByName List.66 List.284 List.89;
ret List.283;
procedure List.4 (List.89, List.90):
let List.297 : U64 = 1i64;
let List.296 : List I64 = CallByName List.65 List.89 List.297;
let List.295 : List I64 = CallByName List.66 List.296 List.90;
ret List.295;
procedure List.65 (#Attr.2, #Attr.3):
let List.287 : List I64 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.287;
let List.299 : List I64 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.299;
procedure List.66 (#Attr.2, #Attr.3):
let List.286 : List I64 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.286;
let List.298 : List I64 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.298;
procedure Test.0 ():
let Test.2 : List I64 = Array [1i64];

View file

@ -1,16 +1,16 @@
procedure List.4 (List.88, List.89):
let List.285 : U64 = 1i64;
let List.284 : List I64 = CallByName List.65 List.88 List.285;
let List.283 : List I64 = CallByName List.66 List.284 List.89;
ret List.283;
procedure List.4 (List.89, List.90):
let List.297 : U64 = 1i64;
let List.296 : List I64 = CallByName List.65 List.89 List.297;
let List.295 : List I64 = CallByName List.66 List.296 List.90;
ret List.295;
procedure List.65 (#Attr.2, #Attr.3):
let List.287 : List I64 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.287;
let List.299 : List I64 = lowlevel ListReserve #Attr.2 #Attr.3;
ret List.299;
procedure List.66 (#Attr.2, #Attr.3):
let List.286 : List I64 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.286;
let List.298 : List I64 = lowlevel ListAppendUnsafe #Attr.2 #Attr.3;
ret List.298;
procedure Test.1 (Test.2):
let Test.6 : I64 = 42i64;

View file

@ -1,35 +1,35 @@
procedure List.3 (List.85, List.86, List.87):
let List.286 : {List I64, I64} = CallByName List.57 List.85 List.86 List.87;
let List.285 : List I64 = StructAtIndex 0 List.286;
inc List.285;
dec List.286;
ret List.285;
procedure List.3 (List.86, List.87, List.88):
let List.298 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88;
let List.297 : List I64 = StructAtIndex 0 List.298;
inc List.297;
dec List.298;
ret List.297;
procedure List.57 (List.82, List.83, List.84):
let List.291 : U64 = CallByName List.6 List.82;
let List.288 : Int1 = CallByName Num.22 List.83 List.291;
if List.288 then
let List.289 : {List I64, I64} = CallByName List.61 List.82 List.83 List.84;
ret List.289;
procedure List.57 (List.83, List.84, List.85):
let List.303 : U64 = CallByName List.6 List.83;
let List.300 : Int1 = CallByName Num.22 List.84 List.303;
if List.300 then
let List.301 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85;
ret List.301;
else
let List.287 : {List I64, I64} = Struct {List.82, List.84};
ret List.287;
let List.299 : {List I64, I64} = Struct {List.83, List.85};
ret List.299;
procedure List.6 (#Attr.2):
let List.284 : U64 = lowlevel ListLen #Attr.2;
ret List.284;
let List.296 : U64 = lowlevel ListLen #Attr.2;
ret List.296;
procedure List.61 (#Attr.2, #Attr.3, #Attr.4):
let List.290 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4;
ret List.290;
let List.302 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4;
ret List.302;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.189 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.189;
let Num.258 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.258;
procedure Test.1 ():
let Test.8 : List I64 = Array [1i64, 2i64, 3i64];

View file

@ -1,26 +1,26 @@
procedure List.2 (List.77, List.78):
let List.288 : U64 = CallByName List.6 List.77;
let List.285 : Int1 = CallByName Num.22 List.78 List.288;
if List.285 then
let List.287 : I64 = CallByName List.60 List.77 List.78;
let List.286 : [C {}, C I64] = TagId(1) List.287;
ret List.286;
procedure List.2 (List.78, List.79):
let List.300 : U64 = CallByName List.6 List.78;
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
if List.297 then
let List.299 : I64 = CallByName List.60 List.78 List.79;
let List.298 : [C {}, C I64] = TagId(1) List.299;
ret List.298;
else
let List.284 : {} = Struct {};
let List.283 : [C {}, C I64] = TagId(0) List.284;
ret List.283;
let List.296 : {} = Struct {};
let List.295 : [C {}, C I64] = TagId(0) List.296;
ret List.295;
procedure List.6 (#Attr.2):
let List.290 : U64 = lowlevel ListLen #Attr.2;
ret List.290;
let List.302 : U64 = lowlevel ListLen #Attr.2;
ret List.302;
procedure List.60 (#Attr.2, #Attr.3):
let List.289 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.289;
let List.301 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.301;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.188 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.257;
procedure Test.1 (Test.2):
let Test.6 : List I64 = Array [1i64, 2i64, 3i64];

View file

@ -1,14 +1,14 @@
procedure List.6 (#Attr.2):
let List.283 : U64 = lowlevel ListLen #Attr.2;
ret List.283;
let List.295 : U64 = lowlevel ListLen #Attr.2;
ret List.295;
procedure List.6 (#Attr.2):
let List.284 : U64 = lowlevel ListLen #Attr.2;
ret List.284;
let List.296 : U64 = lowlevel ListLen #Attr.2;
ret List.296;
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : U64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.0 ():
let Test.1 : List I64 = Array [1i64, 2i64, 3i64];

View file

@ -1,38 +1,38 @@
procedure List.2 (List.77, List.78):
let List.288 : U64 = CallByName List.6 List.77;
let List.285 : Int1 = CallByName Num.22 List.78 List.288;
if List.285 then
let List.287 : Str = CallByName List.60 List.77 List.78;
let List.286 : [C {}, C Str] = TagId(1) List.287;
ret List.286;
procedure List.2 (List.78, List.79):
let List.300 : U64 = CallByName List.6 List.78;
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
if List.297 then
let List.299 : Str = CallByName List.60 List.78 List.79;
let List.298 : [C {}, C Str] = TagId(1) List.299;
ret List.298;
else
let List.284 : {} = Struct {};
let List.283 : [C {}, C Str] = TagId(0) List.284;
ret List.283;
let List.296 : {} = Struct {};
let List.295 : [C {}, C Str] = TagId(0) List.296;
ret List.295;
procedure List.5 (#Attr.2, #Attr.3):
let List.289 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3;
ret List.289;
let List.301 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3;
ret List.301;
procedure List.6 (#Attr.2):
let List.291 : U64 = lowlevel ListLen #Attr.2;
ret List.291;
let List.303 : U64 = lowlevel ListLen #Attr.2;
ret List.303;
procedure List.60 (#Attr.2, #Attr.3):
let List.290 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.290;
let List.302 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.302;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.188 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.257;
procedure Str.16 (#Attr.2, #Attr.3):
let Str.154 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
ret Str.154;
let Str.194 : Str = lowlevel StrRepeat #Attr.2 #Attr.3;
ret Str.194;
procedure Str.3 (#Attr.2, #Attr.3):
let Str.155 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.155;
let Str.195 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.195;
procedure Test.1 ():
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";

View file

@ -1,36 +1,36 @@
procedure List.2 (List.77, List.78):
let List.288 : U64 = CallByName List.6 List.77;
let List.285 : Int1 = CallByName Num.22 List.78 List.288;
if List.285 then
let List.287 : Str = CallByName List.60 List.77 List.78;
let List.286 : [C {}, C Str] = TagId(1) List.287;
ret List.286;
procedure List.2 (List.78, List.79):
let List.300 : U64 = CallByName List.6 List.78;
let List.297 : Int1 = CallByName Num.22 List.79 List.300;
if List.297 then
let List.299 : Str = CallByName List.60 List.78 List.79;
let List.298 : [C {}, C Str] = TagId(1) List.299;
ret List.298;
else
let List.284 : {} = Struct {};
let List.283 : [C {}, C Str] = TagId(0) List.284;
ret List.283;
let List.296 : {} = Struct {};
let List.295 : [C {}, C Str] = TagId(0) List.296;
ret List.295;
procedure List.5 (#Attr.2, #Attr.3):
inc #Attr.2;
let List.289 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3;
let List.301 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3;
decref #Attr.2;
ret List.289;
ret List.301;
procedure List.6 (#Attr.2):
let List.291 : U64 = lowlevel ListLen #Attr.2;
ret List.291;
let List.303 : U64 = lowlevel ListLen #Attr.2;
ret List.303;
procedure List.60 (#Attr.2, #Attr.3):
let List.290 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.290;
let List.302 : Str = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.302;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.188 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.257;
procedure Str.3 (#Attr.2, #Attr.3):
let Str.155 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.155;
let Str.195 : Str = lowlevel StrConcat #Attr.2 #Attr.3;
ret Str.195;
procedure Test.1 ():
let Test.21 : Str = "lllllllllllllllllllllooooooooooong";

View file

@ -1,31 +1,31 @@
procedure List.3 (List.85, List.86, List.87):
let List.284 : {List I64, I64} = CallByName List.57 List.85 List.86 List.87;
let List.283 : List I64 = StructAtIndex 0 List.284;
inc List.283;
dec List.284;
ret List.283;
procedure List.3 (List.86, List.87, List.88):
let List.296 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88;
let List.295 : List I64 = StructAtIndex 0 List.296;
inc List.295;
dec List.296;
ret List.295;
procedure List.57 (List.82, List.83, List.84):
let List.289 : U64 = CallByName List.6 List.82;
let List.286 : Int1 = CallByName Num.22 List.83 List.289;
if List.286 then
let List.287 : {List I64, I64} = CallByName List.61 List.82 List.83 List.84;
ret List.287;
procedure List.57 (List.83, List.84, List.85):
let List.301 : U64 = CallByName List.6 List.83;
let List.298 : Int1 = CallByName Num.22 List.84 List.301;
if List.298 then
let List.299 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85;
ret List.299;
else
let List.285 : {List I64, I64} = Struct {List.82, List.84};
ret List.285;
let List.297 : {List I64, I64} = Struct {List.83, List.85};
ret List.297;
procedure List.6 (#Attr.2):
let List.290 : U64 = lowlevel ListLen #Attr.2;
ret List.290;
let List.302 : U64 = lowlevel ListLen #Attr.2;
ret List.302;
procedure List.61 (#Attr.2, #Attr.3, #Attr.4):
let List.288 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4;
ret List.288;
let List.300 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4;
ret List.300;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.188 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.257;
procedure Test.2 (Test.3):
let Test.6 : U64 = 0i64;

View file

@ -1,20 +1,20 @@
procedure List.28 (#Attr.2, #Attr.3):
let List.285 : List I64 = lowlevel ListSortWith { xs: `#Attr.#arg1` } #Attr.2 Num.46 #Attr.3;
let List.297 : List I64 = lowlevel ListSortWith { xs: `#Attr.#arg1` } #Attr.2 Num.46 #Attr.3;
let Bool.9 : Int1 = lowlevel ListIsUnique #Attr.2;
if Bool.9 then
ret List.285;
ret List.297;
else
decref #Attr.2;
ret List.285;
ret List.297;
procedure List.54 (List.200):
let List.284 : {} = Struct {};
let List.283 : List I64 = CallByName List.28 List.200 List.284;
ret List.283;
procedure List.54 (List.201):
let List.296 : {} = Struct {};
let List.295 : List I64 = CallByName List.28 List.201 List.296;
ret List.295;
procedure Num.46 (#Attr.2, #Attr.3):
let Num.188 : U8 = lowlevel NumCompare #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : U8 = lowlevel NumCompare #Attr.2 #Attr.3;
ret Num.257;
procedure Test.0 ():
let Test.2 : List I64 = Array [4i64, 3i64, 2i64, 1i64];

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.0 ():
let Test.19 : I64 = 41i64;

View file

@ -1,6 +1,6 @@
procedure Num.21 (#Attr.2, #Attr.3):
let Num.190 : I64 = lowlevel NumMul #Attr.2 #Attr.3;
ret Num.190;
let Num.259 : I64 = lowlevel NumMul #Attr.2 #Attr.3;
ret Num.259;
procedure Test.1 (Test.6):
let Test.21 : Int1 = false;

View file

@ -1,14 +1,14 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Num.20 (#Attr.2, #Attr.3):
let Num.189 : I64 = lowlevel NumSub #Attr.2 #Attr.3;
ret Num.189;
let Num.258 : I64 = lowlevel NumSub #Attr.2 #Attr.3;
ret Num.258;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.190 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.190;
let Num.259 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.259;
procedure Test.1 (Test.24, Test.25, Test.26):
joinpoint Test.12 Test.2 Test.3 Test.4:

View file

@ -1,47 +1,47 @@
procedure List.2 (List.77, List.78):
let List.298 : U64 = CallByName List.6 List.77;
let List.295 : Int1 = CallByName Num.22 List.78 List.298;
if List.295 then
let List.297 : I64 = CallByName List.60 List.77 List.78;
let List.296 : [C {}, C I64] = TagId(1) List.297;
ret List.296;
procedure List.2 (List.78, List.79):
let List.310 : U64 = CallByName List.6 List.78;
let List.307 : Int1 = CallByName Num.22 List.79 List.310;
if List.307 then
let List.309 : I64 = CallByName List.60 List.78 List.79;
let List.308 : [C {}, C I64] = TagId(1) List.309;
ret List.308;
else
let List.294 : {} = Struct {};
let List.293 : [C {}, C I64] = TagId(0) List.294;
ret List.293;
let List.306 : {} = Struct {};
let List.305 : [C {}, C I64] = TagId(0) List.306;
ret List.305;
procedure List.3 (List.85, List.86, List.87):
let List.286 : {List I64, I64} = CallByName List.57 List.85 List.86 List.87;
let List.285 : List I64 = StructAtIndex 0 List.286;
inc List.285;
dec List.286;
ret List.285;
procedure List.3 (List.86, List.87, List.88):
let List.298 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88;
let List.297 : List I64 = StructAtIndex 0 List.298;
inc List.297;
dec List.298;
ret List.297;
procedure List.57 (List.82, List.83, List.84):
let List.303 : U64 = CallByName List.6 List.82;
let List.300 : Int1 = CallByName Num.22 List.83 List.303;
if List.300 then
let List.301 : {List I64, I64} = CallByName List.61 List.82 List.83 List.84;
ret List.301;
procedure List.57 (List.83, List.84, List.85):
let List.315 : U64 = CallByName List.6 List.83;
let List.312 : Int1 = CallByName Num.22 List.84 List.315;
if List.312 then
let List.313 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85;
ret List.313;
else
let List.299 : {List I64, I64} = Struct {List.82, List.84};
ret List.299;
let List.311 : {List I64, I64} = Struct {List.83, List.85};
ret List.311;
procedure List.6 (#Attr.2):
let List.304 : U64 = lowlevel ListLen #Attr.2;
ret List.304;
let List.316 : U64 = lowlevel ListLen #Attr.2;
ret List.316;
procedure List.60 (#Attr.2, #Attr.3):
let List.305 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.305;
let List.317 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.317;
procedure List.61 (#Attr.2, #Attr.3, #Attr.4):
let List.302 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4;
ret List.302;
let List.314 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4;
ret List.314;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.190 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.190;
let Num.259 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.259;
procedure Test.1 (Test.2):
let Test.28 : U64 = 0i64;

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.1 (Test.4):
let Test.2 : I64 = StructAtIndex 0 Test.4;

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.1 (Test.4):
let Test.2 : I64 = 10i64;

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.1 (Test.2):
let Test.3 : I64 = StructAtIndex 0 Test.2;

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.1 (Test.2):
let Test.3 : I64 = 10i64;

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : U32 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : U32 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.1 (Test.2):
let Test.9 : U32 = 0i64;

View file

@ -1,47 +1,47 @@
procedure List.2 (List.77, List.78):
let List.298 : U64 = CallByName List.6 List.77;
let List.295 : Int1 = CallByName Num.22 List.78 List.298;
if List.295 then
let List.297 : I64 = CallByName List.60 List.77 List.78;
let List.296 : [C {}, C I64] = TagId(1) List.297;
ret List.296;
procedure List.2 (List.78, List.79):
let List.310 : U64 = CallByName List.6 List.78;
let List.307 : Int1 = CallByName Num.22 List.79 List.310;
if List.307 then
let List.309 : I64 = CallByName List.60 List.78 List.79;
let List.308 : [C {}, C I64] = TagId(1) List.309;
ret List.308;
else
let List.294 : {} = Struct {};
let List.293 : [C {}, C I64] = TagId(0) List.294;
ret List.293;
let List.306 : {} = Struct {};
let List.305 : [C {}, C I64] = TagId(0) List.306;
ret List.305;
procedure List.3 (List.85, List.86, List.87):
let List.286 : {List I64, I64} = CallByName List.57 List.85 List.86 List.87;
let List.285 : List I64 = StructAtIndex 0 List.286;
inc List.285;
dec List.286;
ret List.285;
procedure List.3 (List.86, List.87, List.88):
let List.298 : {List I64, I64} = CallByName List.57 List.86 List.87 List.88;
let List.297 : List I64 = StructAtIndex 0 List.298;
inc List.297;
dec List.298;
ret List.297;
procedure List.57 (List.82, List.83, List.84):
let List.303 : U64 = CallByName List.6 List.82;
let List.300 : Int1 = CallByName Num.22 List.83 List.303;
if List.300 then
let List.301 : {List I64, I64} = CallByName List.61 List.82 List.83 List.84;
ret List.301;
procedure List.57 (List.83, List.84, List.85):
let List.315 : U64 = CallByName List.6 List.83;
let List.312 : Int1 = CallByName Num.22 List.84 List.315;
if List.312 then
let List.313 : {List I64, I64} = CallByName List.61 List.83 List.84 List.85;
ret List.313;
else
let List.299 : {List I64, I64} = Struct {List.82, List.84};
ret List.299;
let List.311 : {List I64, I64} = Struct {List.83, List.85};
ret List.311;
procedure List.6 (#Attr.2):
let List.304 : U64 = lowlevel ListLen #Attr.2;
ret List.304;
let List.316 : U64 = lowlevel ListLen #Attr.2;
ret List.316;
procedure List.60 (#Attr.2, #Attr.3):
let List.305 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.305;
let List.317 : I64 = lowlevel ListGetUnsafe #Attr.2 #Attr.3;
ret List.317;
procedure List.61 (#Attr.2, #Attr.3, #Attr.4):
let List.302 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4;
ret List.302;
let List.314 : {List I64, I64} = lowlevel ListReplaceUnsafe #Attr.2 #Attr.3 #Attr.4;
ret List.314;
procedure Num.22 (#Attr.2, #Attr.3):
let Num.190 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.190;
let Num.259 : Int1 = lowlevel NumLt #Attr.2 #Attr.3;
ret Num.259;
procedure Test.1 (Test.2, Test.3, Test.4):
let Test.29 : [C {}, C I64] = CallByName List.2 Test.4 Test.3;

View file

@ -1,10 +1,10 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.189 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.189;
let Num.258 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.258;
procedure Num.21 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumMul #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumMul #Attr.2 #Attr.3;
ret Num.257;
procedure Test.1 (Test.2, Test.3):
let Test.17 : U8 = GetTagId Test.2;

View file

@ -1,10 +1,10 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.189 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.189;
let Num.258 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.258;
procedure Num.21 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumMul #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumMul #Attr.2 #Attr.3;
ret Num.257;
procedure Test.6 (Test.8, #Attr.12):
let Test.4 : I64 = UnionAtIndex (Id 0) (Index 0) #Attr.12;

View file

@ -1,10 +1,10 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Num.20 (#Attr.2, #Attr.3):
let Num.189 : I64 = lowlevel NumSub #Attr.2 #Attr.3;
ret Num.189;
let Num.258 : I64 = lowlevel NumSub #Attr.2 #Attr.3;
ret Num.258;
procedure Test.1 (Test.15, Test.16):
joinpoint Test.7 Test.2 Test.3:

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.0 ():
let Test.19 : I64 = 41i64;

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.0 ():
let Test.5 : I64 = 2i64;

View file

@ -1,6 +1,6 @@
procedure Num.19 (#Attr.2, #Attr.3):
let Num.188 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.188;
let Num.257 : I64 = lowlevel NumAdd #Attr.2 #Attr.3;
ret Num.257;
procedure Test.0 ():
let Test.15 : I64 = 3i64;

View file

@ -1718,3 +1718,65 @@ fn call_function_in_empty_list_unbound() {
"#
)
}
#[mono_test]
fn instantiate_annotated_as_recursive_alias_toplevel() {
indoc!(
r#"
app "test" provides [it] to "./platform"
Value : [Nil, Array (List Value)]
foo : [Nil]*
foo = Nil
it : Value
it = foo
"#
)
}
#[mono_test]
fn instantiate_annotated_as_recursive_alias_polymorphic_expr() {
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
Value : [Nil, Array (List Value)]
foo : [Nil]*
foo = Nil
it : Value
it = foo
it
"#
)
}
#[mono_test]
fn instantiate_annotated_as_recursive_alias_multiple_polymorphic_expr() {
indoc!(
r#"
app "test" provides [main] to "./platform"
main =
Value : [Nil, Array (List Value)]
foo : [Nil]*
foo = Nil
v1 : Value
v1 = foo
Value2 : [Nil, B U16, Array (List Value)]
v2 : Value2
v2 = foo
{v1, v2}
"#
)
}

View file

@ -342,6 +342,7 @@ fn find_names_needed(
solved,
recursion_var,
unspecialized,
ambient_function: _,
}) => {
for slice_index in solved.variables() {
let slice = subs[slice_index];
@ -721,6 +722,7 @@ fn write_content<'a>(
solved,
recursion_var,
unspecialized,
ambient_function: _,
}) => {
debug_assert!(env.debug.print_lambda_sets);

View file

@ -187,6 +187,7 @@ pub fn to_type(
Type::ClosureTag {
name: *name,
captures: new_args,
ambient_function: var_store.fresh(),
}
}
FunctionOrTagUnion(tag_name, symbol, ext) => {

View file

@ -15,8 +15,7 @@ use crate::unification_table::{self, UnificationTable};
// if your changes cause this number to go down, great!
// please change it to the lower number.
// if it went up, maybe check that the change is really required
roc_error_macros::assert_sizeof_all!(Descriptor, 5 * 8);
roc_error_macros::assert_sizeof_all!(Content, 3 * 8 + 4);
roc_error_macros::assert_sizeof_all!(Descriptor, 5 * 8 + 4);
roc_error_macros::assert_sizeof_all!(FlatType, 3 * 8);
roc_error_macros::assert_sizeof_all!(UnionTags, 12);
roc_error_macros::assert_sizeof_all!(RecordFields, 2 * 8);
@ -84,7 +83,7 @@ impl SubsHeader {
fn from_subs(subs: &Subs, exposed_vars_by_symbol: usize) -> Self {
// TODO what do we do with problems? they should
// be reported and then removed from Subs I think
debug_assert!(subs.problems.is_empty());
debug_assert!(subs.problems.is_empty(), "{:?}", &subs.problems);
Self {
utable: subs.utable.len() as u64,
@ -341,10 +340,10 @@ impl UlsOfVar {
}
/// NOTE: this does not follow unification links.
pub fn drain(self) -> impl Iterator<Item = (Variable, impl Iterator<Item = Variable>)> {
pub fn drain(self) -> impl Iterator<Item = (Variable, VecSet<Variable>)> {
self.0
.into_iter()
.map(|(v, set): (Variable, VecSet<Variable>)| (v, set.into_iter()))
.map(|(v, set): (Variable, VecSet<Variable>)| (v, set))
}
pub fn len(&self) -> usize {
@ -779,8 +778,20 @@ impl<'a> fmt::Debug for SubsFmtContent<'a> {
fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt::Result {
match this {
Content::FlexVar(name) => write!(f, "Flex({:?})", name),
Content::FlexAbleVar(name, symbol) => write!(f, "FlexAble({:?}, {:?})", name, symbol),
Content::FlexVar(name) => {
let name = match name {
Some(index) => subs[*index].as_str(),
None => "_",
};
write!(f, "Flex({})", name)
}
Content::FlexAbleVar(name, symbol) => {
let name = match name {
Some(index) => subs[*index].as_str(),
None => "_",
};
write!(f, "FlexAble({}, {:?})", name, symbol)
}
Content::RigidVar(name) => write!(f, "Rigid({:?})", name),
Content::RigidAbleVar(name, symbol) => write!(f, "RigidAble({:?}, {:?})", name, symbol),
Content::RecursionVar {
@ -809,6 +820,7 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt:
solved,
recursion_var,
unspecialized,
ambient_function: ambient_function_var,
}) => {
write!(f, "LambdaSet([")?;
@ -839,7 +851,7 @@ fn subs_fmt_content(this: &Content, subs: &Subs, f: &mut fmt::Formatter) -> fmt:
region
)?;
}
write!(f, ")")
write!(f, ", ^<{:?}>)", ambient_function_var)
}
Content::RangedNumber(range) => {
write!(f, "RangedNumber( {:?})", range)
@ -1920,6 +1932,7 @@ impl Subs {
recursive: Variable,
solved_lambdas: UnionLambdas,
unspecialized_lambdas: SubsSlice<Uls>,
ambient_function_var: Variable,
) {
let (rec_var, new_tags) = self.mark_union_recursive_help(recursive, solved_lambdas);
@ -1927,6 +1940,7 @@ impl Subs {
solved: new_tags,
recursion_var: OptVariable::from(rec_var),
unspecialized: unspecialized_lambdas,
ambient_function: ambient_function_var,
});
self.set_content(recursive, new_lambda_set);
@ -2167,10 +2181,11 @@ impl From<Content> for Descriptor {
}
}
roc_error_macros::assert_sizeof_all!(Content, 3 * 8 + 4);
roc_error_macros::assert_sizeof_all!(Content, 4 * 8);
roc_error_macros::assert_sizeof_all!((Symbol, AliasVariables, Variable), 2 * 8 + 4);
roc_error_macros::assert_sizeof_all!(AliasVariables, 8);
roc_error_macros::assert_sizeof_all!(FlatType, 3 * 8);
roc_error_macros::assert_sizeof_all!(LambdaSet, 3 * 8 + 4);
roc_error_macros::assert_sizeof_aarch64!((Variable, Option<Lowercase>), 4 * 8);
roc_error_macros::assert_sizeof_wasm!((Variable, Option<Lowercase>), 4 * 4);
@ -2244,6 +2259,12 @@ pub struct LambdaSet {
pub recursion_var: OptVariable,
/// Lambdas we won't know until an ability specialization is resolved.
pub unspecialized: SubsSlice<Uls>,
/// Backlink to the function wrapping this lambda set.
/// This should never be unified against when unifying a lambda set; that would evidently
/// introduce an infinite unification.
/// This is used for the ambient lambda set unification algorithm.
pub ambient_function: Variable,
}
#[derive(Clone, Copy, Debug, Default)]
@ -3135,6 +3156,7 @@ fn occurs(
solved,
recursion_var,
unspecialized: _,
ambient_function: _,
}) => {
let mut new_seen = seen.to_owned();
new_seen.push(root_var);
@ -3318,6 +3340,7 @@ fn explicit_substitute(
solved,
recursion_var,
unspecialized,
ambient_function: ambient_function_var,
}) => {
// NOTE recursion_var is not substituted, verify that this is correct!
let new_solved = explicit_substitute_union(subs, from, to, solved, seen);
@ -3332,6 +3355,7 @@ fn explicit_substitute(
solved: new_solved,
recursion_var,
unspecialized,
ambient_function: ambient_function_var,
}),
);
@ -3439,6 +3463,7 @@ fn get_var_names(
solved,
recursion_var,
unspecialized,
ambient_function: _,
}) => {
let taken_names = get_var_names_union(subs, solved, taken_names);
let mut taken_names = match recursion_var.into_variable() {
@ -3966,6 +3991,7 @@ impl StorageSubs {
pub fn as_inner_mut(&mut self) -> &mut Subs {
&mut self.subs
}
pub fn as_inner(&self) -> &Subs {
&self.subs
}
@ -4156,10 +4182,12 @@ impl StorageSubs {
solved,
recursion_var,
unspecialized,
ambient_function: ambient_function_var,
}) => LambdaSet(self::LambdaSet {
solved: Self::offset_lambda_set(offsets, *solved),
recursion_var: recursion_var.map(|v| Self::offset_variable(offsets, v)),
unspecialized: Self::offset_uls_slice(offsets, *unspecialized),
ambient_function: Self::offset_variable(offsets, *ambient_function_var),
}),
RangedNumber(range) => RangedNumber(*range),
Error => Content::Error,
@ -4578,6 +4606,7 @@ fn storage_copy_var_to_help(env: &mut StorageCopyVarToEnv<'_>, var: Variable) ->
solved,
recursion_var,
unspecialized,
ambient_function: ambient_function_var,
}) => {
let new_solved = storage_copy_union(env, solved);
let new_rec_var = recursion_var.map(|v| storage_copy_var_to_help(env, v));
@ -4593,10 +4622,13 @@ fn storage_copy_var_to_help(env: &mut StorageCopyVarToEnv<'_>, var: Variable) ->
env.target[target_index] = Uls(new_var, sym, region);
}
let new_ambient_function_var = storage_copy_var_to_help(env, ambient_function_var);
let new_content = LambdaSet(self::LambdaSet {
solved: new_solved,
recursion_var: new_rec_var,
unspecialized: new_unspecialized,
ambient_function: new_ambient_function_var,
});
env.target.set(copy, make_descriptor(new_content));
copy
@ -5036,6 +5068,7 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
solved,
recursion_var,
unspecialized,
ambient_function: ambient_function_var,
}) => {
let new_solved = copy_union(env, max_rank, solved);
let new_rec_var =
@ -5054,10 +5087,13 @@ fn copy_import_to_help(env: &mut CopyImportEnv<'_>, max_rank: Rank, var: Variabl
}
}
let new_ambient_function_var = copy_import_to_help(env, max_rank, ambient_function_var);
let new_content = LambdaSet(self::LambdaSet {
solved: new_solved,
recursion_var: new_rec_var,
unspecialized: new_unspecialized,
ambient_function: new_ambient_function_var,
});
env.target.set(copy, make_descriptor(new_content));
@ -5214,6 +5250,7 @@ fn instantiate_rigids_help(subs: &mut Subs, max_rank: Rank, initial: Variable) {
solved,
recursion_var,
unspecialized,
ambient_function: _,
}) => {
for slice_index in solved.variables() {
let slice = subs.variable_slices[slice_index.index as usize];

View file

@ -240,8 +240,11 @@ pub enum Type {
ClosureTag {
name: Symbol,
captures: Vec<Type>,
ambient_function: Variable,
},
UnspecializedLambdaSet {
unspecialized: Uls,
},
UnspecializedLambdaSet(Uls),
DelayedAlias(AliasCommon),
Alias {
symbol: Symbol,
@ -312,11 +315,18 @@ impl Clone for Type {
Self::FunctionOrTagUnion(arg0, arg1, arg2) => {
Self::FunctionOrTagUnion(arg0.clone(), *arg1, arg2.clone())
}
Self::ClosureTag { name, captures } => Self::ClosureTag {
Self::ClosureTag {
name,
captures,
ambient_function,
} => Self::ClosureTag {
name: *name,
captures: captures.clone(),
ambient_function: *ambient_function,
},
Self::UnspecializedLambdaSet { unspecialized } => Self::UnspecializedLambdaSet {
unspecialized: *unspecialized,
},
Self::UnspecializedLambdaSet(uls) => Self::UnspecializedLambdaSet(*uls),
Self::DelayedAlias(arg0) => Self::DelayedAlias(arg0.clone()),
Self::Alias {
symbol,
@ -622,7 +632,11 @@ impl fmt::Debug for Type {
}
}
}
Type::ClosureTag { name, captures } => {
Type::ClosureTag {
name,
captures,
ambient_function: _,
} => {
write!(f, "ClosureTag(")?;
write!(f, "{:?}, ", name)?;
@ -655,8 +669,8 @@ impl fmt::Debug for Type {
Type::RangedNumber(range_vars) => {
write!(f, "Ranged({:?})", range_vars)
}
Type::UnspecializedLambdaSet(uls) => {
write!(f, "{:?}", uls)
Type::UnspecializedLambdaSet { unspecialized } => {
write!(f, "{:?}", unspecialized)
}
}
}
@ -713,7 +727,11 @@ impl Type {
stack.push(closure);
stack.push(ret);
}
ClosureTag { name: _, captures } => stack.extend(captures),
ClosureTag {
name: _,
captures,
ambient_function: _,
} => stack.extend(captures),
TagUnion(tags, ext) => {
for (_, args) in tags {
stack.extend(args.iter_mut());
@ -795,7 +813,9 @@ impl Type {
stack.extend(args);
}
RangedNumber(_) => {}
UnspecializedLambdaSet(Uls(v, _, _)) => {
UnspecializedLambdaSet {
unspecialized: Uls(v, _, _),
} => {
debug_assert!(
substitutions.get(v).is_none(),
"unspecialized lambda sets should never be substituted before solving"
@ -824,7 +844,11 @@ impl Type {
stack.push(closure);
stack.push(ret);
}
ClosureTag { name: _, captures } => {
ClosureTag {
name: _,
captures,
ambient_function: _,
} => {
stack.extend(captures);
}
TagUnion(tags, ext) => {
@ -910,7 +934,9 @@ impl Type {
stack.extend(args);
}
RangedNumber(_) => {}
UnspecializedLambdaSet(Uls(v, _, _)) => {
UnspecializedLambdaSet {
unspecialized: Uls(v, _, _),
} => {
debug_assert!(
substitutions.get(v).is_none(),
"unspecialized lambda sets should never be substituted before solving"
@ -1013,7 +1039,7 @@ impl Type {
Ok(())
}
RangedNumber(_) => Ok(()),
UnspecializedLambdaSet(..) => Ok(()),
UnspecializedLambdaSet { .. } => Ok(()),
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => Ok(()),
}
}
@ -1070,7 +1096,9 @@ impl Type {
Apply(symbol, _, _) if *symbol == rep_symbol => true,
Apply(_, args, _) => args.iter().any(|arg| arg.contains_symbol(rep_symbol)),
RangedNumber(_) => false,
UnspecializedLambdaSet(Uls(_, sym, _)) => *sym == rep_symbol,
UnspecializedLambdaSet {
unspecialized: Uls(_, sym, _),
} => *sym == rep_symbol,
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => false,
}
}
@ -1093,10 +1121,14 @@ impl Type {
|| args.iter().any(|arg| arg.contains_variable(rep_variable))
}
FunctionOrTagUnion(_, _, ext) => Self::contains_variable_ext(ext, rep_variable),
ClosureTag { name: _, captures } => {
captures.iter().any(|t| t.contains_variable(rep_variable))
}
UnspecializedLambdaSet(Uls(v, _, _)) => *v == rep_variable,
ClosureTag {
name: _,
captures,
ambient_function: _,
} => captures.iter().any(|t| t.contains_variable(rep_variable)),
UnspecializedLambdaSet {
unspecialized: Uls(v, _, _),
} => *v == rep_variable,
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
Self::contains_variable_ext(ext, rep_variable)
|| tags
@ -1384,7 +1416,7 @@ impl Type {
}
}
RangedNumber(_) => {}
UnspecializedLambdaSet(..) => {}
UnspecializedLambdaSet { .. } => {}
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {}
}
}
@ -1520,7 +1552,9 @@ fn symbols_help(initial: &Type) -> Vec<Symbol> {
output.push(*alias);
}
RangedNumber(_) => {}
UnspecializedLambdaSet(Uls(_, _sym, _)) => {
UnspecializedLambdaSet {
unspecialized: Uls(_, _sym, _),
} => {
// ignore the member symbol because unspecialized lambda sets are internal-only
}
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {}
@ -1565,12 +1599,18 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
variables_help(ext, accum);
}
}
ClosureTag { name: _, captures } => {
ClosureTag {
name: _,
captures,
ambient_function: _,
} => {
for t in captures {
variables_help(t, accum);
}
}
UnspecializedLambdaSet(Uls(v, _, _)) => {
UnspecializedLambdaSet {
unspecialized: Uls(v, _, _),
} => {
accum.insert(*v);
}
TagUnion(tags, ext) => {
@ -1700,7 +1740,11 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
variables_help_detailed(ext, accum);
}
}
ClosureTag { name: _, captures } => {
ClosureTag {
name: _,
captures,
ambient_function: _,
} => {
for t in captures {
variables_help_detailed(t, accum);
}
@ -1721,7 +1765,9 @@ fn variables_help_detailed(tipe: &Type, accum: &mut VariableDetail) {
variables_help_detailed(ext, accum);
}
}
UnspecializedLambdaSet(Uls(var, _, _)) => {
UnspecializedLambdaSet {
unspecialized: Uls(var, _, _),
} => {
accum.type_variables.insert(*var);
}
RecursiveTagUnion(rec, tags, ext) => {
@ -2728,7 +2774,9 @@ fn instantiate_lambda_sets_as_unspecialized(
let mut new_uls = || {
region += 1;
Type::UnspecializedLambdaSet(Uls(able_var, ability_member, region))
Type::UnspecializedLambdaSet {
unspecialized: Uls(able_var, ability_member, region),
}
};
while let Some(typ) = stack.pop() {
@ -2762,10 +2810,14 @@ fn instantiate_lambda_sets_as_unspecialized(
Type::FunctionOrTagUnion(_, _, ext) => {
stack.extend(ext.iter_mut());
}
Type::ClosureTag { name: _, captures } => {
Type::ClosureTag {
name: _,
captures,
ambient_function: _,
} => {
stack.extend(captures.iter_mut().rev());
}
Type::UnspecializedLambdaSet(..) => {
Type::UnspecializedLambdaSet { .. } => {
internal_error!("attempting to re-instantiate ULS")
}
Type::DelayedAlias(AliasCommon {
@ -2846,7 +2898,9 @@ mod test {
macro_rules! check_uls {
($typ:expr, $region:literal) => {{
match $typ {
Type::UnspecializedLambdaSet(Uls(var1, member1, $region)) => {
Type::UnspecializedLambdaSet {
unspecialized: Uls(var1, member1, $region),
} => {
assert!(var1 == able_var && member1 == member)
}
_ => panic!(),

View file

@ -968,6 +968,7 @@ fn unify_lambda_set<M: MetaCollector>(
solved: UnionLabels::default(),
recursion_var: OptVariable::NONE,
unspecialized: SubsSlice::default(),
ambient_function: subs.fresh_unnamed_flex_var(),
};
extract_specialization_lambda_set(subs, ctx, lambda_set, zero_lambda_set)
@ -1013,6 +1014,7 @@ fn extract_specialization_lambda_set<M: MetaCollector>(
solved: member_solved,
recursion_var: member_rec_var,
unspecialized: member_uls_slice,
ambient_function: _,
} = ability_member_proto_lset;
debug_assert!(
@ -1207,6 +1209,64 @@ fn separate_union_lambdas(
}
}
fn unify_unspecialized_lambdas<M: MetaCollector>(
subs: &mut Subs,
pool: &mut Pool,
uls1: SubsSlice<Uls>,
uls2: SubsSlice<Uls>,
) -> Result<SubsSlice<Uls>, Outcome<M>> {
// For now we merge all variables of unspecialized lambdas in a lambda set that share the same
// ability member/region.
// See the section "A property that's lost, and how we can hold on to it" of
// solve/docs/ambient_lambda_set_specialization.md to see how we can loosen this restriction.
// Note that we don't need to update the bookkeeping of variable -> lambda set to be resolved,
// because if we had v1 -> lset1, and now lset1 ~ lset2, then afterward either lset1 still
// resolves to itself or re-points to lset2.
// In either case the merged unspecialized lambda sets will be there.
match (uls1.is_empty(), uls2.is_empty()) {
(true, true) => Ok(SubsSlice::default()),
(false, true) => Ok(uls1),
(true, false) => Ok(uls2),
(false, false) => {
let mut all_uls = (subs.get_subs_slice(uls1).iter())
.chain(subs.get_subs_slice(uls2))
.map(|&Uls(var, sym, region)| {
// Take the root key to deduplicate
Uls(subs.get_root_key_without_compacting(var), sym, region)
})
.collect::<Vec<_>>();
// Arrange into partitions of (_, member, region).
all_uls.sort_by_key(|&Uls(_, sym, region)| (sym, region));
// Now merge the variables of unspecialized lambdas pointing to the same
// member/region.
let mut j = 1;
while j < all_uls.len() {
let i = j - 1;
let Uls(var_i, sym_i, region_i) = all_uls[i];
let Uls(var_j, sym_j, region_j) = all_uls[j];
if sym_i == sym_j && region_i == region_j {
let outcome = unify_pool(subs, pool, var_i, var_j, Mode::EQ);
if !outcome.mismatches.is_empty() {
return Err(outcome);
}
// Keep the Uls in position `i` and remove the one in position `j`.
all_uls.remove(j);
} else {
// Keep both Uls, look at the next one.
j += 1;
}
}
Ok(SubsSlice::extend_new(
&mut subs.unspecialized_lambda_sets,
all_uls,
))
}
}
}
fn unify_lambda_set_help<M: MetaCollector>(
subs: &mut Subs,
pool: &mut Pool,
@ -1221,13 +1281,20 @@ fn unify_lambda_set_help<M: MetaCollector>(
solved: solved1,
recursion_var: rec1,
unspecialized: uls1,
ambient_function: ambient_function_var1,
} = lset1;
let LambdaSet {
solved: solved2,
recursion_var: rec2,
unspecialized: uls2,
ambient_function: ambient_function_var2,
} = lset2;
// Assumed precondition: the ambient functions have already been unified, or are in the process
// of being unified - otherwise, how could we have reached unification of lambda sets?
let _ = ambient_function_var2;
let ambient_function_var = ambient_function_var1;
debug_assert!(
(rec1.into_variable().into_iter())
.chain(rec2.into_variable().into_iter())
@ -1266,26 +1333,11 @@ fn unify_lambda_set_help<M: MetaCollector>(
(None, None) => OptVariable::NONE,
};
// Combine the unspecialized lambda sets as needed. Note that we don't need to update the
// bookkeeping of variable -> lambda set to be resolved, because if we had v1 -> lset1, and
// now lset1 ~ lset2, then afterward either lset1 still resolves to itself or re-points to
// lset2. In either case the merged unspecialized lambda sets will be there.
let merged_unspecialized = match (uls1.is_empty(), uls2.is_empty()) {
(true, true) => SubsSlice::default(),
(false, true) => uls1,
(true, false) => uls2,
(false, false) => {
let mut all_uls = (subs.get_subs_slice(uls1).iter())
.chain(subs.get_subs_slice(uls2))
.map(|&Uls(var, sym, region)| {
// Take the root key to deduplicate
Uls(subs.get_root_key_without_compacting(var), sym, region)
})
.collect::<Vec<_>>();
all_uls.sort();
all_uls.dedup();
SubsSlice::extend_new(&mut subs.unspecialized_lambda_sets, all_uls)
let merged_unspecialized = match unify_unspecialized_lambdas(subs, pool, uls1, uls2) {
Ok(merged) => merged,
Err(outcome) => {
debug_assert!(!outcome.mismatches.is_empty());
return outcome;
}
};
@ -1294,6 +1346,7 @@ fn unify_lambda_set_help<M: MetaCollector>(
solved: new_solved,
recursion_var,
unspecialized: merged_unspecialized,
ambient_function: ambient_function_var,
});
merge(subs, ctx, new_lambda_set)
@ -1927,8 +1980,9 @@ fn maybe_mark_union_recursive(subs: &mut Subs, union_var: Variable) {
solved,
recursion_var: OptVariable::NONE,
unspecialized,
ambient_function: ambient_function_var,
}) => {
subs.mark_lambda_set_recursive(v, solved, unspecialized);
subs.mark_lambda_set_recursive(v, solved, unspecialized, ambient_function_var);
continue 'outer;
}
_ => { /* fall through */ }
@ -2681,10 +2735,12 @@ fn unify_function_or_tag_union_and_func<M: MetaCollector>(
{
let union_tags = UnionLambdas::tag_without_arguments(subs, tag_symbol);
let ambient_function_var = if left { ctx.first } else { ctx.second };
let lambda_set_content = LambdaSet(self::LambdaSet {
solved: union_tags,
recursion_var: OptVariable::NONE,
unspecialized: SubsSlice::default(),
ambient_function: ambient_function_var,
});
let tag_lambda_set = register(