merge remote/main and update mono

This commit is contained in:
Luke Boswell 2023-04-20 07:55:18 +10:00
commit ab4ac1c494
No known key found for this signature in database
GPG key ID: F6DB3C9DB47377B0
96 changed files with 4746 additions and 3422 deletions

5
Cargo.lock generated
View file

@ -5084,8 +5084,12 @@ dependencies = [
"pretty_assertions",
"regex",
"roc_builtins",
"roc_collections",
"roc_derive",
"roc_load",
"roc_module",
"roc_mono",
"roc_packaging",
"roc_parse",
"roc_problem",
"roc_reporting",
@ -5195,6 +5199,7 @@ dependencies = [
"cli_utils",
"indoc",
"roc_build",
"roc_command_utils",
"roc_linker",
"roc_load",
"roc_mono",

View file

@ -474,7 +474,7 @@ mod cli_run {
}
#[test]
#[serial(basic_cli_url)]
#[serial(zig_platform_parser_package_basic_cli_url)]
#[cfg_attr(windows, ignore)]
fn hello_world() {
test_roc_app_slim(
@ -533,8 +533,9 @@ mod cli_run {
)
}
// zig_platform_parser_package_basic_cli_url use to be split up but then things could get stuck
#[test]
#[serial(zig_platform)]
#[serial(zig_platform_parser_package_basic_cli_url)]
#[cfg_attr(windows, ignore)]
fn platform_switching_zig() {
test_roc_app_slim(
@ -915,8 +916,7 @@ mod cli_run {
}
#[test]
#[serial(parser_package)]
#[serial(zig_platform)]
#[serial(zig_platform_parser_package_basic_cli_url)]
#[cfg_attr(windows, ignore)]
fn parse_movies_csv() {
test_roc_app_slim(
@ -929,8 +929,7 @@ mod cli_run {
}
#[test]
#[serial(parser_package)]
#[serial(basic_cli_url)]
#[serial(zig_platform_parser_package_basic_cli_url)]
#[cfg_attr(windows, ignore)]
fn parse_letter_counts() {
test_roc_app_slim(

View file

@ -1450,6 +1450,10 @@ fn expr_spec<'a>(
Reset {
symbol,
update_mode,
}
| ResetRef {
symbol,
update_mode,
} => {
let tag_value_id = env.symbols[symbol];

View file

@ -1151,10 +1151,7 @@ fn link_macos(
output_path.set_extension("dylib");
(
vec!["-dylib", "-undefined", "dynamic_lookup", "-no_fixup_chains"],
output_path,
)
(vec!["-dylib"], output_path)
}
LinkType::None => internal_error!("link_macos should not be called with link type of none"),
};

View file

@ -52,15 +52,34 @@ interface Decode
Bool.{ Bool },
]
## Error types when decoding a `List U8` of utf-8 bytes using a [Decoder]
DecodeError : [TooShort]
## Return type of a [Decoder].
##
## This is can be useful when creating a [custom](#custom) decoder or when
## using [fromBytesPartial](#fromBytesPartial). For example writing unit tests,
## such as;
## ```
## expect
## input = "\"hello\", " |> Str.toUtf8
## actual = Decode.fromBytesPartial input Json.fromUtf8
## expected = Ok "hello"
##
## actual.result == expected
## ```
DecodeResult val : { result : Result val DecodeError, rest : List U8 }
## Decodes a `List U8` of utf-8 bytes where `val` is the type of the decoded
## value, and `fmt` is a [Decoder] which implements the [DecoderFormatting]
## ability
Decoder val fmt := List U8, fmt -> DecodeResult val | fmt has DecoderFormatting
## Definition of the [Decoding] ability
Decoding has
decoder : Decoder val fmt | val has Decoding, fmt has DecoderFormatting
## Definition of the [DecoderFormatting] ability
DecoderFormatting has
u8 : Decoder U8 fmt | fmt has DecoderFormatting
u16 : Decoder U16 fmt | fmt has DecoderFormatting
@ -96,15 +115,46 @@ DecoderFormatting has
## `finalizer` should produce the tuple value from the decoded `state`.
tuple : state, (state, Nat -> [Next (Decoder state fmt), TooLong]), (state -> Result val DecodeError) -> Decoder val fmt | fmt has DecoderFormatting
## Build a custom [Decoder] function. For example the implementation of
## `decodeBool` could be defined as follows;
##
## ```
## decodeBool = Decode.custom \bytes, @Json {} ->
## when bytes is
## ['f', 'a', 'l', 's', 'e', ..] -> { result: Ok Bool.false, rest: List.drop bytes 5 }
## ['t', 'r', 'u', 'e', ..] -> { result: Ok Bool.true, rest: List.drop bytes 4 }
## _ -> { result: Err TooShort, rest: bytes }
## ```
custom : (List U8, fmt -> DecodeResult val) -> Decoder val fmt | fmt has DecoderFormatting
custom = \decode -> @Decoder decode
## Decode a `List U8` utf-8 bytes using a specific [Decoder] function
decodeWith : List U8, Decoder val fmt, fmt -> DecodeResult val | fmt has DecoderFormatting
decodeWith = \bytes, @Decoder decode, fmt -> decode bytes fmt
## Decode a `List U8` utf-8 bytes and return a [DecodeResult](#DecodeResult)
## ```
## expect
## input = "\"hello\", " |> Str.toUtf8
## actual = Decode.fromBytesPartial input Json.fromUtf8
## expected = Ok "hello"
##
## actual.result == expected
## ```
fromBytesPartial : List U8, fmt -> DecodeResult val | val has Decoding, fmt has DecoderFormatting
fromBytesPartial = \bytes, fmt -> decodeWith bytes decoder fmt
## Decode a `List U8` utf-8 bytes and return a [Result] with no leftover bytes
## expected. If successful returns `Ok val`, however, if there are bytes
## remaining returns `Err Leftover (List U8)`.
## ```
## expect
## input = "\"hello\", " |> Str.toUtf8
## actual = Decode.fromBytes input Json.fromUtf8
## expected = Ok "hello"
##
## actual == expected
## ```
fromBytes : List U8, fmt -> Result val [Leftover (List U8)]DecodeError | val has Decoding, fmt has DecoderFormatting
fromBytes = \bytes, fmt ->
when fromBytesPartial bytes fmt is
@ -116,5 +166,6 @@ fromBytes = \bytes, fmt ->
else
Err (Leftover rest)
## Transform the `val` of a [DecodeResult]
mapResult : DecodeResult a, (a -> b) -> DecodeResult b
mapResult = \{ result, rest }, mapper -> { result: Result.map result mapper, rest }

View file

@ -1258,51 +1258,6 @@ fn shallow_dealias_with_scope<'a>(scope: &'a mut Scope, typ: &'a Type) -> &'a Ty
result
}
pub fn instantiate_and_freshen_alias_type(
var_store: &mut VarStore,
introduced_variables: &mut IntroducedVariables,
type_variables: &[Loc<AliasVar>],
type_arguments: Vec<Type>,
lambda_set_variables: &[LambdaSet],
mut actual_type: Type,
) -> (Vec<(Lowercase, Type)>, Vec<LambdaSet>, Type) {
let mut substitutions = ImMap::default();
let mut type_var_to_arg = Vec::new();
for (loc_var, arg_ann) in type_variables.iter().zip(type_arguments.into_iter()) {
let name = loc_var.value.name.clone();
let var = loc_var.value.var;
substitutions.insert(var, arg_ann.clone());
type_var_to_arg.push((name.clone(), arg_ann));
}
// make sure the recursion variable is freshly instantiated
if let Type::RecursiveTagUnion(rvar, _, _) = &mut actual_type {
let new = var_store.fresh();
substitutions.insert(*rvar, Type::Variable(new));
*rvar = new;
}
// make sure hidden variables are freshly instantiated
let mut new_lambda_set_variables = Vec::with_capacity(lambda_set_variables.len());
for typ in lambda_set_variables.iter() {
if let Type::Variable(var) = typ.0 {
let fresh = var_store.fresh();
substitutions.insert(var, Type::Variable(fresh));
introduced_variables.insert_lambda_set(fresh);
new_lambda_set_variables.push(LambdaSet(Type::Variable(fresh)));
} else {
unreachable!("at this point there should be only vars in there");
}
}
// instantiate variables
actual_type.substitute(&substitutions);
(type_var_to_arg, new_lambda_set_variables, actual_type)
}
pub fn freshen_opaque_def(
var_store: &mut VarStore,
opaque: &Alias,
@ -1318,25 +1273,46 @@ pub fn freshen_opaque_def(
})
.collect();
let fresh_type_arguments = fresh_variables
.iter()
.map(|av| Type::Variable(av.var))
.collect();
// NB: We don't introduce the fresh variables here, we introduce them during constraint gen.
// NB: If there are bugs, check whether this is a problem!
let mut introduced_variables = IntroducedVariables::default();
let (_fresh_type_arguments, fresh_lambda_set, fresh_type) = instantiate_and_freshen_alias_type(
var_store,
&mut introduced_variables,
&opaque.type_variables,
fresh_type_arguments,
&opaque.lambda_set_variables,
opaque.typ.clone(),
);
let mut substitutions = ImMap::default();
(fresh_variables, fresh_lambda_set, fresh_type)
// Freshen all type variables used in the opaque.
for (loc_var, fresh_var) in opaque.type_variables.iter().zip(fresh_variables.iter()) {
let old_var = loc_var.value.var;
substitutions.insert(old_var, Type::Variable(fresh_var.var));
// NB: fresh var not introduced in this pass; see above.
}
// Freshen all nested recursion variables.
for &rec_var in opaque.recursion_variables.iter() {
let new = var_store.fresh();
substitutions.insert(rec_var, Type::Variable(new));
}
// Freshen all nested lambda sets.
let mut new_lambda_set_variables = Vec::with_capacity(opaque.lambda_set_variables.len());
for typ in opaque.lambda_set_variables.iter() {
if let Type::Variable(var) = typ.0 {
let fresh = var_store.fresh();
substitutions.insert(var, Type::Variable(fresh));
introduced_variables.insert_lambda_set(fresh);
new_lambda_set_variables.push(LambdaSet(Type::Variable(fresh)));
} else {
unreachable!("at this point there should be only vars in there");
}
}
// Fresh the real type with our new variables.
let actual_type = {
let mut typ = opaque.typ.clone();
typ.substitute(&substitutions);
typ
};
(fresh_variables, new_lambda_set_variables, actual_type)
}
fn insertion_sort_by<T, F>(arr: &mut [T], mut compare: F)

View file

@ -2925,12 +2925,14 @@ fn correct_mutual_recursive_type_alias<'a>(
};
let mut new_lambda_sets = ImSet::default();
let mut new_recursion_variables = ImSet::default();
let mut new_infer_ext_vars = ImSet::default();
alias_type.instantiate_aliases(
alias_region,
&can_instantiate_symbol,
var_store,
&mut new_lambda_sets,
&mut new_recursion_variables,
&mut new_infer_ext_vars,
);
@ -2953,6 +2955,9 @@ fn correct_mutual_recursive_type_alias<'a>(
.map(|var| LambdaSet(Type::Variable(*var))),
);
// add any new recursion variables
alias.recursion_variables.extend(new_recursion_variables);
// add any new infer-in-output extension variables that the instantiation created to the current alias
alias
.infer_ext_in_output_variables

View file

@ -140,7 +140,7 @@ impl Pattern {
RecordDestructure { destructs, .. } => {
// If all destructs are surely exhaustive, then this is surely exhaustive.
destructs.iter().all(|d| match &d.value.typ {
DestructType::Required | DestructType::Optional(_, _) => false,
DestructType::Required | DestructType::Optional(_, _) => true,
DestructType::Guard(_, pat) => pat.value.surely_exhaustive(),
})
}

View file

@ -463,6 +463,7 @@ pub fn create_alias(
let mut hidden = type_variables;
for var in (vars.iter().map(|lv| lv.value.var))
.chain(recursion_variables.iter().copied())
.chain(infer_ext_in_output_variables.iter().copied())
{
hidden.remove(&var);

View file

@ -137,6 +137,10 @@ impl<T> Slice<T> {
pub fn into_iter(&self) -> impl Iterator<Item = Index<T>> {
self.indices().map(|i| Index::new(i as _))
}
pub const fn at(&self, i: usize) -> Index<T> {
Index::new(self.start + i as u32)
}
}
#[derive(PartialEq, Eq)]

View file

@ -2097,6 +2097,7 @@ fn constrain_destructure_def(
loc_pattern,
&mut ftv,
&mut def_pattern_state.headers,
IsRecursiveDef::No,
);
let env = &mut Env {
@ -2674,6 +2675,7 @@ fn constrain_typed_def(
&def.loc_pattern,
&mut ftv,
&mut def_pattern_state.headers,
IsRecursiveDef::No,
);
let env = &mut Env {
@ -3323,6 +3325,12 @@ pub struct InstantiateRigids {
pub new_infer_variables: Vec<Variable>,
}
#[derive(PartialEq, Eq)]
enum IsRecursiveDef {
Yes,
No,
}
fn instantiate_rigids(
types: &mut Types,
constraints: &mut Constraints,
@ -3331,65 +3339,82 @@ fn instantiate_rigids(
loc_pattern: &Loc<Pattern>,
ftv: &mut MutMap<Lowercase, Variable>, // rigids defined before the current annotation
headers: &mut VecMap<Symbol, Loc<TypeOrVar>>,
is_recursive_def: IsRecursiveDef,
) -> InstantiateRigids {
let mut annotation = annotation.clone();
let mut new_rigid_variables: Vec<Variable> = Vec::new();
let mut new_rigid_variables = vec![];
let mut new_infer_variables = vec![];
let mut rigid_substitution: MutMap<Variable, Variable> = MutMap::default();
for named in introduced_vars.iter_named() {
use std::collections::hash_map::Entry::*;
let mut generate_fresh_ann = |types: &mut Types| {
let mut annotation = annotation.clone();
match ftv.entry(named.name().clone()) {
Occupied(occupied) => {
let existing_rigid = occupied.get();
rigid_substitution.insert(named.variable(), *existing_rigid);
}
Vacant(vacant) => {
// It's possible to use this rigid in nested defs
vacant.insert(named.variable());
new_rigid_variables.push(named.variable());
let mut rigid_substitution: MutMap<Variable, Variable> = MutMap::default();
for named in introduced_vars.iter_named() {
use std::collections::hash_map::Entry::*;
match ftv.entry(named.name().clone()) {
Occupied(occupied) => {
let existing_rigid = occupied.get();
rigid_substitution.insert(named.variable(), *existing_rigid);
}
Vacant(vacant) => {
// It's possible to use this rigid in nested defs
vacant.insert(named.variable());
new_rigid_variables.push(named.variable());
}
}
}
// wildcards are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.wildcards.iter().map(|v| v.value));
// lambda set vars are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied());
// ext-infer vars are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.infer_ext_in_output.iter().copied());
new_infer_variables.extend(introduced_vars.inferred.iter().map(|v| v.value));
// Instantiate rigid variables
if !rigid_substitution.is_empty() {
annotation.substitute_variables(&rigid_substitution);
}
types.from_old_type(&annotation)
};
let signature = generate_fresh_ann(types);
{
// If this is a recursive def, we must also generate a fresh annotation to be used as the
// type annotation that will be used in the first def headers introduced during the solving
// of the recursive definition.
//
// That is, this annotation serves as step (1) of XREF(rec-def-strategy). We don't want to
// link to the final annotation, since it may be incomplete (or incorrect, see step (1)).
// So, we generate a fresh annotation here, and return a separate fresh annotation below;
// the latter annotation is the one used to construct the finalized type.
let annotation_index = if is_recursive_def == IsRecursiveDef::Yes {
generate_fresh_ann(types)
} else {
signature
};
let loc_annotation_ref = Loc::at(loc_pattern.region, annotation_index);
if let Pattern::Identifier(symbol) = loc_pattern.value {
let annotation_index = constraints.push_type(types, annotation_index);
headers.insert(symbol, Loc::at(loc_pattern.region, annotation_index));
} else if let Some(new_headers) = crate::pattern::headers_from_annotation(
types,
constraints,
&loc_pattern.value,
&loc_annotation_ref,
) {
headers.extend(new_headers)
}
}
// wildcards are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.wildcards.iter().map(|v| v.value));
// lambda set vars are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.lambda_sets.iter().copied());
// ext-infer vars are always freshly introduced in this annotation
new_rigid_variables.extend(introduced_vars.infer_ext_in_output.iter().copied());
let new_infer_variables: Vec<Variable> =
introduced_vars.inferred.iter().map(|v| v.value).collect();
// Instantiate rigid variables
if !rigid_substitution.is_empty() {
annotation.substitute_variables(&rigid_substitution);
}
let annotation_index = types.from_old_type(&annotation);
// TODO investigate when we can skip this. It seems to only be required for correctness
// for recursive functions. For non-recursive functions the final type is correct, but
// alias information is sometimes lost
//
// Skipping all of this cloning here would be neat!
let loc_annotation_ref = Loc::at(loc_pattern.region, &annotation);
if let Pattern::Identifier(symbol) = loc_pattern.value {
let annotation_index = constraints.push_type(types, annotation_index);
headers.insert(symbol, Loc::at(loc_pattern.region, annotation_index));
} else if let Some(new_headers) = crate::pattern::headers_from_annotation(
types,
constraints,
&loc_pattern.value,
&loc_annotation_ref,
) {
headers.extend(new_headers)
}
InstantiateRigids {
signature: annotation_index,
signature,
new_rigid_variables,
new_infer_variables,
}
@ -3867,7 +3892,7 @@ pub fn rec_defs_help_simple(
}
}
// Strategy for recursive defs:
// NB(rec-def-strategy) Strategy for recursive defs:
//
// 1. Let-generalize all rigid annotations. These are the source of truth we'll solve
// everything else with. If there are circular type errors here, they will be caught
@ -4068,11 +4093,12 @@ fn rec_defs_help(
&def.loc_pattern,
&mut ftv,
&mut def_pattern_state.headers,
IsRecursiveDef::Yes,
);
let is_hybrid = !new_infer_variables.is_empty();
hybrid_and_flex_info.vars.extend(new_infer_variables);
hybrid_and_flex_info.vars.extend(&new_infer_variables);
let signature_index = constraints.push_type(types, signature);
@ -4113,13 +4139,14 @@ fn rec_defs_help(
let region = def.loc_expr.region;
let loc_body_expr = &**loc_body;
let mut state = PatternState {
let mut argument_pattern_state = PatternState {
headers: VecMap::default(),
vars: Vec::with_capacity(arguments.len()),
constraints: Vec::with_capacity(1),
delayed_is_open_constraints: vec![],
};
let mut vars = Vec::with_capacity(state.vars.capacity() + 1);
let mut vars =
Vec::with_capacity(argument_pattern_state.vars.capacity() + 1);
let ret_var = *ret_var;
let closure_var = *closure_var;
let ret_type_index = constraints.push_type(types, ret_type);
@ -4133,7 +4160,7 @@ fn rec_defs_help(
env,
def,
&mut def_pattern_state,
&mut state,
&mut argument_pattern_state,
arguments,
arg_types,
);
@ -4157,27 +4184,31 @@ fn rec_defs_help(
let typ = types.function(pattern_types, lambda_set, ret_type);
constraints.push_type(types, typ)
};
let body_type =
constraints.push_expected_type(NoExpectation(ret_type_index));
let expr_con = constrain_expr(
types,
constraints,
env,
loc_body_expr.region,
&loc_body_expr.value,
body_type,
);
let expr_con = {
let body_type =
constraints.push_expected_type(NoExpectation(ret_type_index));
constrain_expr(
types,
constraints,
env,
loc_body_expr.region,
&loc_body_expr.value,
body_type,
)
};
let expr_con = attach_resolution_constraints(constraints, env, expr_con);
vars.push(*fn_var);
let state_constraints = constraints.and_constraint(state.constraints);
let state_constraints =
constraints.and_constraint(argument_pattern_state.constraints);
let expected_index = constraints.push_expected_type(expected);
let cons = [
constraints.let_constraint(
[],
state.vars,
state.headers,
argument_pattern_state.vars,
argument_pattern_state.headers,
state_constraints,
expr_con,
generalizable,
@ -4216,9 +4247,15 @@ fn rec_defs_help(
} else {
rigid_info.vars.extend(&new_rigid_variables);
let rigids = new_rigid_variables;
let flex = def_pattern_state
.vars
.into_iter()
.chain(new_infer_variables);
rigid_info.constraints.push(constraints.let_constraint(
new_rigid_variables,
def_pattern_state.vars,
rigids,
flex,
[], // no headers introduced (at this level)
def_con,
Constraint::True,
@ -4278,7 +4315,7 @@ fn rec_defs_help(
}
}
// Strategy for recursive defs:
// NB(rec-def-strategy) Strategy for recursive defs:
//
// 1. Let-generalize all rigid annotations. These are the source of truth we'll solve
// everything else with. If there are circular type errors here, they will be caught

View file

@ -5,14 +5,15 @@ use roc_can::expected::{Expected, PExpected};
use roc_can::pattern::Pattern::{self, *};
use roc_can::pattern::{DestructType, ListPatterns, RecordDestruct, TupleDestruct};
use roc_collections::all::{HumanIndex, SendMap};
use roc_collections::soa::Index;
use roc_collections::VecMap;
use roc_module::ident::Lowercase;
use roc_module::symbol::Symbol;
use roc_region::all::{Loc, Region};
use roc_types::subs::Variable;
use roc_types::types::{
AliasKind, Category, OptAbleType, PReason, PatternCategory, Reason, RecordField, Type,
TypeExtension, TypeTag, Types,
AliasKind, AliasShared, Category, OptAbleType, PReason, PatternCategory, Reason, RecordField,
Type, TypeExtension, TypeTag, Types,
};
#[derive(Default, Debug)]
@ -31,10 +32,10 @@ pub struct PatternState {
/// Would add `x => <42>` to the headers (i.e., symbol points to a type variable). If the
/// definition has an annotation, we instead now add `x => Int`.
pub fn headers_from_annotation(
types: &mut Types,
types: &Types,
constraints: &mut Constraints,
pattern: &Pattern,
annotation: &Loc<&Type>,
annotation: &Loc<Index<TypeTag>>,
) -> Option<VecMap<Symbol, Loc<TypeOrVar>>> {
let mut headers = VecMap::default();
// Check that the annotation structurally agrees with the pattern, preventing e.g. `{ x, y } : Int`
@ -51,12 +52,13 @@ pub fn headers_from_annotation(
}
fn headers_from_annotation_help(
types: &mut Types,
types: &Types,
constraints: &mut Constraints,
pattern: &Pattern,
annotation: &Loc<&Type>,
annotation: &Loc<Index<TypeTag>>,
headers: &mut VecMap<Symbol, Loc<TypeOrVar>>,
) -> bool {
let typ = annotation.value;
match pattern {
Identifier(symbol)
| Shadowed(_, _, symbol)
@ -64,20 +66,14 @@ fn headers_from_annotation_help(
ident: symbol,
specializes: _,
} => {
let annotation_index = {
let typ = types.from_old_type(annotation.value);
constraints.push_type(types, typ)
};
let annotation_index = constraints.push_type(types, typ);
let typ = Loc::at(annotation.region, annotation_index);
headers.insert(*symbol, typ);
true
}
As(subpattern, symbol) => {
let annotation_index = {
let typ = types.from_old_type(annotation.value);
constraints.push_type(types, typ)
};
let annotation_index = constraints.push_type(types, typ);
let typ = Loc::at(annotation.region, annotation_index);
headers.insert(*symbol, typ);
@ -94,36 +90,45 @@ fn headers_from_annotation_help(
| SingleQuote(..)
| StrLiteral(_) => true,
RecordDestructure { destructs, .. } => match annotation.value.shallow_dealias() {
Type::Record(fields, _) => {
for loc_destruct in destructs {
let destruct = &loc_destruct.value;
RecordDestructure { destructs, .. } => {
let dealiased = types.shallow_dealias(annotation.value);
match types[dealiased] {
TypeTag::Record(fields) => {
let (field_names, _, field_types) = types.record_fields_slices(fields);
let field_names = &types[field_names];
// NOTE: We ignore both Guard and optionality when
// determining the type of the assigned def (which is what
// gets added to the header here).
//
// For example, no matter whether it's `{ x } = rec` or
// `{ x ? 0 } = rec` or `{ x: 5 } -> ...` in all cases
// the type of `x` within the binding itself is the same.
if let Some(field_type) = fields.get(&destruct.label) {
let field_type_index = {
let typ = types.from_old_type(&field_type.as_inner().clone());
constraints.push_type(types, typ)
};
headers.insert(
destruct.symbol,
Loc::at(annotation.region, field_type_index),
);
} else {
return false;
for loc_destruct in destructs {
let destruct = &loc_destruct.value;
// NOTE: We ignore both Guard and optionality when
// determining the type of the assigned def (which is what
// gets added to the header here).
//
// For example, no matter whether it's `{ x } = rec` or
// `{ x ? 0 } = rec` or `{ x: 5 } -> ...` in all cases
// the type of `x` within the binding itself is the same.
if let Some(i) = field_names
.iter()
.position(|field| field == &destruct.label)
{
let field_type_index = {
let typ = field_types.at(i);
constraints.push_type(types, typ)
};
headers.insert(
destruct.symbol,
Loc::at(annotation.region, field_type_index),
);
} else {
return false;
}
}
true
}
true
TypeTag::EmptyRecord => destructs.is_empty(),
_ => false,
}
Type::EmptyRec => destructs.is_empty(),
_ => false,
},
}
TupleDestructure { destructs: _, .. } => {
todo!();
@ -132,7 +137,7 @@ fn headers_from_annotation_help(
List { patterns, .. } => {
if let Some((_, Some(rest))) = patterns.opt_rest {
let annotation_index = {
let typ = types.from_old_type(annotation.value);
let typ = annotation.value;
constraints.push_type(types, typ)
};
let typ = Loc::at(annotation.region, annotation_index);
@ -152,31 +157,38 @@ fn headers_from_annotation_help(
tag_name,
arguments,
..
} => match annotation.value.shallow_dealias() {
Type::TagUnion(tags, _) => {
if let Some((_, arg_types)) = tags.iter().find(|(name, _)| name == tag_name) {
if !arguments.len() == arg_types.len() {
return false;
}
} => {
let dealiased = types.shallow_dealias(annotation.value);
match types[dealiased] {
TypeTag::TagUnion(tags, _) => {
let (tags, payloads) = types.union_tag_slices(tags);
let tags = &types[tags];
arguments
.iter()
.zip(arg_types.iter())
.all(|(arg_pattern, arg_type)| {
headers_from_annotation_help(
types,
constraints,
&arg_pattern.1.value,
&Loc::at(annotation.region, arg_type),
headers,
)
})
} else {
false
if let Some(i) = tags.iter().position(|name| name == tag_name) {
let arg_types_slice = types[payloads.at(i)];
if !arguments.len() == arg_types_slice.len() {
return false;
}
arguments.iter().zip(arg_types_slice.into_iter()).all(
|(arg_pattern, arg_type)| {
headers_from_annotation_help(
types,
constraints,
&arg_pattern.1.value,
&Loc::at(annotation.region, arg_type),
headers,
)
},
)
} else {
false
}
}
_ => false,
}
_ => false,
},
}
UnwrappedOpaque {
whole_var: _,
@ -185,36 +197,41 @@ fn headers_from_annotation_help(
specialized_def_type: _,
type_arguments: pat_type_arguments,
lambda_set_variables: pat_lambda_set_variables,
} => match &annotation.value {
Type::Alias {
symbol,
kind: AliasKind::Opaque,
actual,
type_arguments,
lambda_set_variables,
infer_ext_in_output_types: _,
} if symbol == opaque
&& type_arguments.len() == pat_type_arguments.len()
&& lambda_set_variables.len() == pat_lambda_set_variables.len() =>
{
let annotation_index = {
let typ = types.from_old_type(annotation.value);
constraints.push_type(types, typ)
};
let typ = Loc::at(annotation.region, annotation_index);
headers.insert(*opaque, typ);
} => {
let typ = annotation.value;
let (_, argument_pat) = &**argument;
headers_from_annotation_help(
types,
constraints,
&argument_pat.value,
&Loc::at(annotation.region, actual),
headers,
)
match types[typ] {
TypeTag::OpaqueAlias { shared, actual } => {
let AliasShared {
symbol,
lambda_set_variables,
..
} = types[shared];
let type_arguments = types.get_type_arguments(typ);
if symbol == *opaque
&& type_arguments.len() == pat_type_arguments.len()
&& lambda_set_variables.len() == pat_lambda_set_variables.len()
{
let annotation_index = constraints.push_type(types, typ);
let typ = Loc::at(annotation.region, annotation_index);
headers.insert(*opaque, typ);
let (_, argument_pat) = &**argument;
headers_from_annotation_help(
types,
constraints,
&argument_pat.value,
&Loc::at(annotation.region, actual),
headers,
)
} else {
false
}
}
_ => false,
}
_ => false,
},
}
}
}

View file

@ -1602,7 +1602,7 @@ trait Backend<'a> {
self.set_last_seen(*sym, stmt);
}
}
Expr::Reset { symbol, .. } => {
Expr::Reset { symbol, .. } | Expr::ResetRef { symbol, .. } => {
self.set_last_seen(*symbol, stmt);
}
Expr::EmptyArray => {}

View file

@ -539,6 +539,7 @@ pub fn build_eq_wrapper<'a, 'ctx, 'env>(
pub fn build_compare_wrapper<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_interner: &mut STLayoutInterner<'a>,
layout_ids: &mut LayoutIds<'a>,
roc_function: FunctionValue<'ctx>,
closure_data_layout: LambdaSet<'a>,
layout: InLayout<'a>,
@ -599,12 +600,11 @@ pub fn build_compare_wrapper<'a, 'ctx, 'env>(
env.builder
.build_pointer_cast(value_ptr2, value_ptr_type, "load_opaque");
let value1 = env
.builder
.new_build_load(value_type, value_cast1, "load_opaque");
let value2 = env
.builder
.new_build_load(value_type, value_cast2, "load_opaque");
let value1 = load_roc_value(env, layout_interner, layout, value_cast1, "load_opaque");
let value2 = load_roc_value(env, layout_interner, layout, value_cast2, "load_opaque");
increment_refcount_layout(env, layout_interner, layout_ids, 1, value1, layout);
increment_refcount_layout(env, layout_interner, layout_ids, 1, value2, layout);
let default = [value1.into(), value2.into()];

View file

@ -1235,6 +1235,53 @@ pub fn build_exp_expr<'a, 'ctx, 'env>(
phi.as_basic_value()
}
}
ResetRef {
symbol,
update_mode,
} => {
let bytes = update_mode.to_bytes();
let update_var = UpdateModeVar(&bytes);
let update_mode = func_spec_solutions
.update_mode(update_var)
.unwrap_or(UpdateMode::Immutable);
let (tag_ptr, layout) = load_symbol_and_layout(scope, symbol);
let tag_ptr = tag_ptr.into_pointer_value();
let ctx = env.context;
let not_unique_block = ctx.append_basic_block(parent, "else_decref");
let cont_block = ctx.append_basic_block(parent, "cont");
let refcount_ptr =
PointerToRefcount::from_ptr_to_data(env, tag_pointer_clear_tag_id(env, tag_ptr));
let is_unique = match update_mode {
UpdateMode::InPlace => env.context.bool_type().const_int(1, false),
UpdateMode::Immutable => refcount_ptr.is_1(env),
};
let parent_block = env.builder.get_insert_block().unwrap();
env.builder
.build_conditional_branch(is_unique, cont_block, not_unique_block);
{
// If reset is used on a shared, non-reusable reference, it behaves
// like dec and returns NULL, which instructs reuse to behave like ctor
env.builder.position_at_end(not_unique_block);
refcount_ptr.decrement(env, layout_interner, layout);
env.builder.build_unconditional_branch(cont_block);
}
{
env.builder.position_at_end(cont_block);
let phi = env.builder.build_phi(tag_ptr.get_type(), "branch");
let null_ptr = tag_ptr.get_type().const_null();
phi.add_incoming(&[(&tag_ptr, parent_block), (&null_ptr, not_unique_block)]);
phi.as_basic_value()
}
}
StructAtIndex {
index, structure, ..

View file

@ -2606,6 +2606,7 @@ pub(crate) fn run_higher_order_low_level<'a, 'ctx, 'env>(
let compare_wrapper = build_compare_wrapper(
env,
layout_interner,
layout_ids,
function,
closure_layout,
element_layout,

View file

@ -1095,6 +1095,8 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
Expr::Reset { symbol: arg, .. } => self.expr_reset(*arg, sym, storage),
Expr::ResetRef { symbol: arg, .. } => self.expr_resetref(*arg, sym, storage),
Expr::RuntimeErrorFunction(_) => {
todo!("Expression `{}`", expr.to_pretty(100, false))
}
@ -2017,6 +2019,33 @@ impl<'a, 'r> WasmBackend<'a, 'r> {
);
}
fn expr_resetref(&mut self, argument: Symbol, ret_symbol: Symbol, ret_storage: &StoredValue) {
let ident_ids = self
.interns
.all_ident_ids
.get_mut(&self.env.module_id)
.unwrap();
// Get an IR expression for the call to the specialized procedure
let layout = self.storage.symbol_layouts[&argument];
let (specialized_call_expr, new_specializations) = self
.helper_proc_gen
.call_resetref_refcount(ident_ids, self.layout_interner, layout, argument);
// If any new specializations were created, register their symbol data
for (spec_sym, spec_layout) in new_specializations.into_iter() {
self.register_helper_proc(spec_sym, spec_layout, ProcSource::Helper);
}
// Generate Wasm code for the IR call expression
self.expr(
ret_symbol,
self.env.arena.alloc(specialized_call_expr),
Layout::BOOL,
ret_storage,
);
}
/// Generate a refcount helper procedure and return a pointer (table index) to it
/// This allows it to be indirectly called from Zig code
pub fn get_refcount_fn_index(&mut self, layout: InLayout<'a>, op: HelperOp) -> u32 {

View file

@ -30,6 +30,7 @@ use roc_module::symbol::{
IdentIds, IdentIdsByModule, Interns, ModuleId, ModuleIds, PQModuleName, PackageModuleIds,
PackageQualified, Symbol,
};
use roc_mono::inc_dec;
use roc_mono::ir::{
CapturedSymbols, ExternalSpecializations, GlueLayouts, LambdaSetId, PartialProc, Proc,
ProcLayout, Procs, ProcsBase, UpdateModeIds,
@ -38,6 +39,7 @@ use roc_mono::layout::LayoutInterner;
use roc_mono::layout::{
GlobalLayoutInterner, LambdaName, Layout, LayoutCache, LayoutProblem, Niche, STLayoutInterner,
};
use roc_mono::reset_reuse;
use roc_packaging::cache::RocCacheDir;
use roc_parse::ast::{
self, CommentOrNewline, Defs, Expr, ExtractSpaces, Pattern, Spaced, StrLiteral, TypeAnnotation,
@ -3087,6 +3089,8 @@ fn update<'a>(
std::mem::swap(&mut state.layout_interner, &mut taken);
taken
};
#[allow(unused_mut)]
let mut layout_interner = layout_interner
.unwrap()
.expect("outstanding references to global layout interener, but we just drained all layout caches");
@ -3098,9 +3102,17 @@ fn update<'a>(
let ident_ids = state.constrained_ident_ids.get_mut(&module_id).unwrap();
Proc::insert_reset_reuse_operations(
inc_dec::insert_inc_dec_operations(
arena,
&mut layout_interner,
&layout_interner,
&mut state.procedures,
);
debug_print_ir!(state, &layout_interner, ROC_PRINT_IR_AFTER_REFCOUNT);
reset_reuse::insert_reset_reuse_operations(
arena,
&layout_interner,
module_id,
ident_ids,
&mut update_mode_ids,
@ -3109,23 +3121,6 @@ fn update<'a>(
debug_print_ir!(state, &layout_interner, ROC_PRINT_IR_AFTER_RESET_REUSE);
let host_exposed_procs = bumpalo::collections::Vec::from_iter_in(
state.exposed_to_host.top_level_values.keys().copied(),
arena,
);
Proc::insert_refcount_operations(
arena,
&layout_interner,
module_id,
ident_ids,
&mut update_mode_ids,
&mut state.procedures,
&host_exposed_procs,
);
debug_print_ir!(state, &layout_interner, ROC_PRINT_IR_AFTER_REFCOUNT);
// This is not safe with the new non-recursive RC updates that we do for tag unions
//
// Proc::optimize_refcount_operations(

View file

@ -12,9 +12,6 @@ use roc_collections::ReferenceMatrix;
use roc_module::low_level::LowLevel;
use roc_module::symbol::Symbol;
pub(crate) const OWNED: bool = false;
pub(crate) const BORROWED: bool = true;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Ownership {
Owned,
@ -22,6 +19,14 @@ pub enum Ownership {
}
impl Ownership {
pub fn is_owned(&self) -> bool {
matches!(self, Ownership::Owned)
}
pub fn is_borrowed(&self) -> bool {
matches!(self, Ownership::Borrowed)
}
/// For reference-counted types (lists, (big) strings, recursive tags), owning a value
/// means incrementing its reference count. Hence, we prefer borrowing for these types
fn from_layout(layout: &Layout) -> Self {
@ -285,21 +290,6 @@ impl<'a> ParamMap<'a> {
.into_bump_slice()
}
fn init_borrow_args_always_owned(
arena: &'a Bump,
ps: &'a [(InLayout<'a>, Symbol)],
) -> &'a [Param<'a>] {
Vec::from_iter_in(
ps.iter().map(|(layout, symbol)| Param {
ownership: Ownership::Owned,
layout: *layout,
symbol: *symbol,
}),
arena,
)
.into_bump_slice()
}
fn visit_proc(
&mut self,
arena: &'a Bump,
@ -307,11 +297,6 @@ impl<'a> ParamMap<'a> {
proc: &Proc<'a>,
key: (Symbol, ProcLayout<'a>),
) {
if proc.must_own_arguments {
self.visit_proc_always_owned(arena, interner, proc, key);
return;
}
let index: usize = self.get_param_offset(interner, key.0, key.1).into();
for (i, param) in Self::init_borrow_args(arena, interner, proc.args)
@ -325,26 +310,6 @@ impl<'a> ParamMap<'a> {
self.visit_stmt(arena, interner, proc.name.name(), &proc.body);
}
fn visit_proc_always_owned(
&mut self,
arena: &'a Bump,
interner: &STLayoutInterner<'a>,
proc: &Proc<'a>,
key: (Symbol, ProcLayout<'a>),
) {
let index: usize = self.get_param_offset(interner, key.0, key.1).into();
for (i, param) in Self::init_borrow_args_always_owned(arena, proc.args)
.iter()
.copied()
.enumerate()
{
self.declarations[index + i] = param;
}
self.visit_stmt(arena, interner, proc.name.name(), &proc.body);
}
fn visit_stmt(
&mut self,
arena: &'a Bump,
@ -447,7 +412,7 @@ impl<'a> BorrowInfState<'a> {
fn update_param_map_help(&mut self, ps: &[Param<'a>]) -> &'a [Param<'a>] {
let mut new_ps = Vec::with_capacity_in(ps.len(), self.arena);
new_ps.extend(ps.iter().map(|p| {
if p.ownership == Ownership::Owned {
if p.ownership.is_owned() {
*p
} else if self.is_owned(p.symbol) {
self.modified = true;
@ -473,7 +438,7 @@ impl<'a> BorrowInfState<'a> {
let ps = &mut param_map.declarations[index..][..length];
for p in ps.iter_mut() {
if p.ownership == Ownership::Owned {
if p.ownership.is_owned() {
// do nothing
} else if self.is_owned(p.symbol) {
self.modified = true;
@ -497,7 +462,7 @@ impl<'a> BorrowInfState<'a> {
debug_assert_eq!(xs.len(), ps.len());
for (x, p) in xs.iter().zip(ps.iter()) {
if p.ownership == Ownership::Owned {
if p.ownership.is_owned() {
self.own_var(*x);
}
}
@ -506,13 +471,10 @@ impl<'a> BorrowInfState<'a> {
/// This looks at an application `f x1 x2 x3`
/// If the parameter (based on the definition of `f`) is owned,
/// then the argument must also be owned
fn own_args_using_bools(&mut self, xs: &[Symbol], ps: &[bool]) {
fn own_args_using_bools(&mut self, xs: &[Symbol], ps: &[Ownership]) {
debug_assert_eq!(xs.len(), ps.len());
for (x, borrow) in xs.iter().zip(ps.iter()) {
if !borrow {
self.own_var(*x);
}
for (x, _) in xs.iter().zip(ps.iter()).filter(|(_, o)| o.is_owned()) {
self.own_var(*x);
}
}
@ -633,44 +595,44 @@ impl<'a> BorrowInfState<'a> {
match op {
ListMap { xs } => {
// own the list if the function wants to own the element
if function_ps[0].ownership == Ownership::Owned {
if function_ps[0].ownership.is_owned() {
self.own_var(*xs);
}
}
ListMap2 { xs, ys } => {
// own the lists if the function wants to own the element
if function_ps[0].ownership == Ownership::Owned {
if function_ps[0].ownership.is_owned() {
self.own_var(*xs);
}
if function_ps[1].ownership == Ownership::Owned {
if function_ps[1].ownership.is_owned() {
self.own_var(*ys);
}
}
ListMap3 { xs, ys, zs } => {
// own the lists if the function wants to own the element
if function_ps[0].ownership == Ownership::Owned {
if function_ps[0].ownership.is_owned() {
self.own_var(*xs);
}
if function_ps[1].ownership == Ownership::Owned {
if function_ps[1].ownership.is_owned() {
self.own_var(*ys);
}
if function_ps[2].ownership == Ownership::Owned {
if function_ps[2].ownership.is_owned() {
self.own_var(*zs);
}
}
ListMap4 { xs, ys, zs, ws } => {
// own the lists if the function wants to own the element
if function_ps[0].ownership == Ownership::Owned {
if function_ps[0].ownership.is_owned() {
self.own_var(*xs);
}
if function_ps[1].ownership == Ownership::Owned {
if function_ps[1].ownership.is_owned() {
self.own_var(*ys);
}
if function_ps[2].ownership == Ownership::Owned {
if function_ps[2].ownership.is_owned() {
self.own_var(*zs);
}
if function_ps[3].ownership == Ownership::Owned {
if function_ps[3].ownership.is_owned() {
self.own_var(*ws);
}
}
@ -742,7 +704,7 @@ impl<'a> BorrowInfState<'a> {
self.if_is_owned_then_own(z, *x);
}
Reset { symbol: x, .. } => {
Reset { symbol: x, .. } | ResetRef { symbol: x, .. } => {
self.own_var(z);
self.own_var(*x);
}
@ -961,22 +923,22 @@ impl<'a> BorrowInfState<'a> {
}
}
pub fn foreign_borrow_signature(arena: &Bump, arity: usize) -> &[bool] {
pub fn foreign_borrow_signature(arena: &Bump, arity: usize) -> &[Ownership] {
// NOTE this means that Roc is responsible for cleaning up resources;
// the host cannot (currently) take ownership
let all = bumpalo::vec![in arena; BORROWED; arity];
let all = bumpalo::vec![in arena; Ownership::Borrowed; arity];
all.into_bump_slice()
}
pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[bool] {
pub fn lowlevel_borrow_signature(arena: &Bump, op: LowLevel) -> &[Ownership] {
use LowLevel::*;
// TODO is true or false more efficient for non-refcounted layouts?
let irrelevant = OWNED;
let irrelevant = Ownership::Owned;
let function = irrelevant;
let closure_data = irrelevant;
let owned = OWNED;
let borrowed = BORROWED;
let owned = Ownership::Owned;
let borrowed = Ownership::Borrowed;
// Here we define the borrow signature of low-level operations
//

View file

@ -213,7 +213,7 @@ fn eq_tag_union<'a>(
layout_interner,
union_layout,
tags,
None,
NullableId::None,
),
Recursive(tags) => eq_tag_union_help(
@ -223,7 +223,7 @@ fn eq_tag_union<'a>(
layout_interner,
union_layout,
tags,
None,
NullableId::None,
),
NonNullableUnwrapped(field_layouts) => {
@ -235,7 +235,7 @@ fn eq_tag_union<'a>(
layout_interner,
union_layout,
tags,
None,
NullableId::None,
)
}
@ -249,7 +249,7 @@ fn eq_tag_union<'a>(
layout_interner,
union_layout,
other_tags,
Some(nullable_id),
NullableId::Wrapped(nullable_id),
),
NullableUnwrapped {
@ -262,7 +262,7 @@ fn eq_tag_union<'a>(
layout_interner,
union_layout,
root.arena.alloc([other_fields]),
Some(nullable_id as TagIdIntType),
NullableId::Unwrapped(nullable_id),
),
};
@ -271,6 +271,12 @@ fn eq_tag_union<'a>(
body
}
enum NullableId {
None,
Wrapped(TagIdIntType),
Unwrapped(bool),
}
fn eq_tag_union_help<'a>(
root: &mut CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
@ -278,7 +284,7 @@ fn eq_tag_union_help<'a>(
layout_interner: &mut STLayoutInterner<'a>,
union_layout: UnionLayout<'a>,
tag_layouts: &'a [&'a [InLayout<'a>]],
nullable_id: Option<TagIdIntType>,
nullable_id: NullableId,
) -> Stmt<'a> {
let tailrec_loop = JoinPointId(root.create_symbol(ident_ids, "tailrec_loop"));
let is_non_recursive = matches!(union_layout, UnionLayout::NonRecursive(_));
@ -340,33 +346,48 @@ fn eq_tag_union_help<'a>(
let mut tag_branches = Vec::with_capacity_in(tag_layouts.len(), root.arena);
// If there's a null tag, check it first. We might not need to load any data from memory.
if let Some(id) = nullable_id {
tag_branches.push((id as u64, BranchInfo::None, Stmt::Ret(Symbol::BOOL_TRUE)))
match nullable_id {
NullableId::Wrapped(id) => {
tag_branches.push((id as u64, BranchInfo::None, Stmt::Ret(Symbol::BOOL_TRUE)))
}
NullableId::Unwrapped(id) => tag_branches.push((
id as TagIdIntType as u64,
BranchInfo::None,
Stmt::Ret(Symbol::BOOL_TRUE),
)),
_ => (),
}
let mut tag_id: TagIdIntType = 0;
for field_layouts in tag_layouts.iter().take(tag_layouts.len() - 1) {
if let Some(null_id) = nullable_id {
if tag_id == null_id as TagIdIntType {
tag_id += 1;
let default_tag = if let NullableId::Unwrapped(tag_id) = nullable_id {
(!tag_id) as TagIdIntType
} else {
let mut tag_id: TagIdIntType = 0;
for field_layouts in tag_layouts.iter().take(tag_layouts.len() - 1) {
if let NullableId::Wrapped(null_id) = nullable_id {
if tag_id == null_id as TagIdIntType {
tag_id += 1;
}
}
let tag_stmt = eq_tag_fields(
root,
ident_ids,
ctx,
layout_interner,
tailrec_loop,
union_layout,
field_layouts,
operands,
tag_id,
);
tag_branches.push((tag_id as u64, BranchInfo::None, tag_stmt));
tag_id += 1;
}
let tag_stmt = eq_tag_fields(
root,
ident_ids,
ctx,
layout_interner,
tailrec_loop,
union_layout,
field_layouts,
operands,
tag_id,
);
tag_branches.push((tag_id as u64, BranchInfo::None, tag_stmt));
tag_id += 1;
}
tag_id
};
let tag_switch_stmt = Stmt::Switch {
cond_symbol: tag_id_a,
@ -383,7 +404,7 @@ fn eq_tag_union_help<'a>(
union_layout,
tag_layouts.last().unwrap(),
operands,
tag_id,
default_tag,
)),
),
ret_layout: LAYOUT_BOOL,

View file

@ -31,6 +31,7 @@ pub enum HelperOp {
Dec,
DecRef(JoinPointId),
Reset,
ResetRef,
Eq,
}
@ -158,11 +159,42 @@ impl<'a> CodeGenHelp<'a> {
layout_interner: &mut STLayoutInterner<'a>,
layout: InLayout<'a>,
argument: Symbol,
) -> (Expr<'a>, Vec<'a, (Symbol, ProcLayout<'a>)>) {
self.call_refcount(ident_ids, layout_interner, layout, argument, false)
}
/**
Call a resetref operation. It is similar to reset except it does not recursively decrement it's children when unique.
*/
pub fn call_resetref_refcount(
&mut self,
ident_ids: &mut IdentIds,
layout_interner: &mut STLayoutInterner<'a>,
layout: InLayout<'a>,
argument: Symbol,
) -> (Expr<'a>, Vec<'a, (Symbol, ProcLayout<'a>)>) {
self.call_refcount(ident_ids, layout_interner, layout, argument, true)
}
/**
Call either a reset or a resetref refcount operation.
*/
fn call_refcount(
&mut self,
ident_ids: &mut IdentIds,
layout_interner: &mut STLayoutInterner<'a>,
layout: InLayout<'a>,
argument: Symbol,
resetref: bool,
) -> (Expr<'a>, Vec<'a, (Symbol, ProcLayout<'a>)>) {
let mut ctx = Context {
new_linker_data: Vec::new_in(self.arena),
recursive_union: None,
op: HelperOp::Reset,
op: if resetref {
HelperOp::ResetRef
} else {
HelperOp::Reset
},
};
let proc_name = self.find_or_create_proc(ident_ids, &mut ctx, layout_interner, layout);
@ -262,7 +294,7 @@ impl<'a> CodeGenHelp<'a> {
let arg = self.replace_rec_ptr(ctx, layout_interner, layout);
match ctx.op {
Dec | DecRef(_) => (LAYOUT_UNIT, self.arena.alloc([arg])),
Reset => (layout, self.arena.alloc([layout])),
Reset | ResetRef => (layout, self.arena.alloc([layout])),
Inc => (LAYOUT_UNIT, self.arena.alloc([arg, self.layout_isize])),
Eq => (LAYOUT_BOOL, self.arena.alloc([arg, arg])),
}
@ -347,6 +379,17 @@ impl<'a> CodeGenHelp<'a> {
Symbol::ARG_1,
),
),
ResetRef => (
layout,
refcount::refcount_resetref_proc_body(
self,
ident_ids,
ctx,
layout_interner,
layout,
Symbol::ARG_1,
),
),
Eq => (
LAYOUT_BOOL,
equality::eq_generic(self, ident_ids, ctx, layout_interner, layout),
@ -360,7 +403,7 @@ impl<'a> CodeGenHelp<'a> {
let inc_amount = (self.layout_isize, ARG_2);
self.arena.alloc([roc_value, inc_amount])
}
Dec | DecRef(_) | Reset => self.arena.alloc([roc_value]),
Dec | DecRef(_) | Reset | ResetRef => self.arena.alloc([roc_value]),
Eq => self.arena.alloc([roc_value, (layout, ARG_2)]),
}
};
@ -372,7 +415,6 @@ impl<'a> CodeGenHelp<'a> {
closure_data_layout: None,
ret_layout,
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: false,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
});
@ -411,6 +453,7 @@ impl<'a> CodeGenHelp<'a> {
niche: Niche::NONE,
},
HelperOp::DecRef(_) => unreachable!("No generated Proc for DecRef"),
HelperOp::ResetRef => unreachable!("No generated Proc for ResetRef"),
HelperOp::Eq => ProcLayout {
arguments: self.arena.alloc([layout, layout]),
result: LAYOUT_BOOL,

View file

@ -1,6 +1,7 @@
#![allow(clippy::too_many_arguments)]
use bumpalo::collections::vec::Vec;
use bumpalo::collections::CollectIn;
use roc_module::low_level::{LowLevel, LowLevel::*};
use roc_module::symbol::{IdentIds, Symbol};
use roc_target::PtrWidth;
@ -256,42 +257,6 @@ pub fn refcount_reset_proc_body<'a>(
)
};
let alloc_addr_stmt = {
let alignment = root.create_symbol(ident_ids, "alignment");
let alignment_expr = Expr::Literal(Literal::Int(
(layout_interner
.get(layout)
.alignment_bytes(layout_interner, root.target_info) as i128)
.to_ne_bytes(),
));
let alloc_addr = root.create_symbol(ident_ids, "alloc_addr");
let alloc_addr_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::NumSubWrap,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: root.arena.alloc([addr, alignment]),
});
Stmt::Let(
alignment,
alignment_expr,
root.layout_isize,
root.arena.alloc(
//
Stmt::Let(
alloc_addr,
alloc_addr_expr,
root.layout_isize,
root.arena.alloc(
//
Stmt::Ret(alloc_addr),
),
),
),
)
};
let rc_contents_stmt = refcount_union_contents(
root,
ident_ids,
@ -303,7 +268,7 @@ pub fn refcount_reset_proc_body<'a>(
structure,
tag_id_sym,
tag_id_layout,
alloc_addr_stmt,
Stmt::Ret(addr),
);
tag_id_stmt(root.arena.alloc(
@ -413,6 +378,166 @@ pub fn refcount_reset_proc_body<'a>(
rc_ptr_stmt
}
pub fn refcount_resetref_proc_body<'a>(
root: &mut CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
ctx: &mut Context<'a>,
layout_interner: &mut STLayoutInterner<'a>,
layout: InLayout<'a>,
structure: Symbol,
) -> Stmt<'a> {
let rc_ptr = root.create_symbol(ident_ids, "rc_ptr");
let rc = root.create_symbol(ident_ids, "rc");
let refcount_1 = root.create_symbol(ident_ids, "refcount_1");
let is_unique = root.create_symbol(ident_ids, "is_unique");
let addr = root.create_symbol(ident_ids, "addr");
let union_layout = match layout_interner.get(layout) {
Layout::Union(u) => u,
_ => unimplemented!("Reset is only implemented for UnionLayout"),
};
// Whenever we recurse into a child layout we will want to Decrement
ctx.op = HelperOp::Dec;
ctx.recursive_union = Some(union_layout);
let recursion_ptr = layout_interner.insert(Layout::RecursivePointer(layout));
// Reset structure is unique. Return a pointer to the allocation.
let then_stmt = {
let alignment = root.create_symbol(ident_ids, "alignment");
let alignment_int = layout_interner
.get(layout)
.allocation_alignment_bytes(layout_interner, root.target_info);
let alignment_expr = Expr::Literal(Literal::Int((alignment_int as i128).to_ne_bytes()));
let alloc_addr = root.create_symbol(ident_ids, "alloc_addr");
let alloc_addr_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::NumSubWrap,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: root.arena.alloc([addr, alignment]),
});
Stmt::Let(
alignment,
alignment_expr,
root.layout_isize,
root.arena.alloc(
//
Stmt::Let(
alloc_addr,
alloc_addr_expr,
root.layout_isize,
root.arena.alloc(
//
Stmt::Ret(alloc_addr),
),
),
),
)
};
// Reset structure is not unique. Decrement it and return a NULL pointer.
let else_stmt = {
let decrement_unit = root.create_symbol(ident_ids, "decrement_unit");
let decrement_expr = root
.call_specialized_op(
ident_ids,
ctx,
layout_interner,
layout,
root.arena.alloc([structure]),
)
.unwrap();
let decrement_stmt = |next| Stmt::Let(decrement_unit, decrement_expr, LAYOUT_UNIT, next);
// Zero
let zero = root.create_symbol(ident_ids, "zero");
let zero_expr = Expr::Literal(Literal::Int(0i128.to_ne_bytes()));
let zero_stmt = |next| Stmt::Let(zero, zero_expr, root.layout_isize, next);
// Null pointer with union layout
let null = root.create_symbol(ident_ids, "null");
let null_stmt =
|next| let_lowlevel(root.arena, root.layout_isize, null, PtrCast, &[zero], next);
decrement_stmt(root.arena.alloc(
//
zero_stmt(root.arena.alloc(
//
null_stmt(root.arena.alloc(
//
Stmt::Ret(null),
)),
)),
))
};
let if_stmt = Stmt::Switch {
cond_symbol: is_unique,
cond_layout: LAYOUT_BOOL,
branches: root.arena.alloc([(1, BranchInfo::None, then_stmt)]),
default_branch: (BranchInfo::None, root.arena.alloc(else_stmt)),
ret_layout: layout,
};
// Uniqueness test
let is_unique_stmt = {
let_lowlevel(
root.arena,
LAYOUT_BOOL,
is_unique,
Eq,
&[rc, refcount_1],
root.arena.alloc(if_stmt),
)
};
// Constant for unique refcount
let refcount_1_encoded = match root.target_info.ptr_width() {
PtrWidth::Bytes4 => i32::MIN as i128,
PtrWidth::Bytes8 => i64::MIN as i128,
}
.to_ne_bytes();
let refcount_1_expr = Expr::Literal(Literal::Int(refcount_1_encoded));
let refcount_1_stmt = Stmt::Let(
refcount_1,
refcount_1_expr,
root.layout_isize,
root.arena.alloc(is_unique_stmt),
);
// Refcount value
let rc_expr = Expr::UnionAtIndex {
structure: rc_ptr,
tag_id: 0,
union_layout: root.union_refcount,
index: 0,
};
let rc_stmt = Stmt::Let(
rc,
rc_expr,
root.layout_isize,
root.arena.alloc(refcount_1_stmt),
);
// Refcount pointer
let rc_ptr_stmt = {
rc_ptr_from_data_ptr_help(
root,
ident_ids,
structure,
rc_ptr,
union_layout.stores_tag_id_in_pointer(root.target_info),
root.arena.alloc(rc_stmt),
addr,
recursion_ptr,
)
};
rc_ptr_stmt
}
fn rc_return_stmt<'a>(
root: &CodeGenHelp<'a>,
ident_ids: &mut IdentIds,
@ -517,12 +642,12 @@ pub fn rc_ptr_from_data_ptr_help<'a>(
let rc_addr_sym = root.create_symbol(ident_ids, "rc_addr");
let sub_expr = Expr::Call(Call {
call_type: CallType::LowLevel {
op: LowLevel::NumSub,
op: LowLevel::NumSubSaturated,
update_mode: UpdateModeId::BACKEND_DUMMY,
},
arguments: root.arena.alloc([addr_sym, ptr_size_sym]),
});
let sub_stmt = |next| Stmt::Let(rc_addr_sym, sub_expr, root.layout_isize, next);
let sub_stmt = |next| Stmt::Let(rc_addr_sym, sub_expr, Layout::usize(root.target_info), next);
// Typecast the refcount address from integer to pointer
let cast_expr = Expr::Call(Call {
@ -594,6 +719,7 @@ fn modify_refcount<'a>(
}
HelperOp::Dec | HelperOp::DecRef(_) => {
debug_assert!(alignment >= root.target_info.ptr_width() as u32);
let alignment_sym = root.create_symbol(ident_ids, "alignment");
let alignment_expr = Expr::Literal(Literal::Int((alignment as i128).to_ne_bytes()));
let alignment_stmt = |next| Stmt::Let(alignment_sym, alignment_expr, LAYOUT_U32, next);
@ -771,7 +897,10 @@ fn refcount_list<'a>(
//
let rc_ptr = root.create_symbol(ident_ids, "rc_ptr");
let elem_alignment = layout_interner.alignment_bytes(elem_layout);
let alignment = Ord::max(
root.target_info.ptr_width() as u32,
layout_interner.alignment_bytes(elem_layout),
);
let ret_stmt = rc_return_stmt(root, ident_ids, ctx);
let modify_list = modify_refcount(
@ -779,7 +908,7 @@ fn refcount_list<'a>(
ident_ids,
ctx,
rc_ptr,
elem_alignment,
alignment,
arena.alloc(ret_stmt),
);
@ -1277,6 +1406,13 @@ fn refcount_union_contents<'a>(
// (Order is important, to avoid use-after-free for Dec)
let following = Stmt::Jump(jp_contents_modified, &[]);
let field_layouts = field_layouts
.iter()
.copied()
.enumerate()
.collect_in::<Vec<_>>(root.arena)
.into_bump_slice();
let fields_stmt = refcount_tag_fields(
root,
ident_ids,
@ -1341,8 +1477,8 @@ fn refcount_union_rec<'a>(
let rc_structure_stmt = {
let rc_ptr = root.create_symbol(ident_ids, "rc_ptr");
let alignment =
Layout::Union(union_layout).alignment_bytes(layout_interner, root.target_info);
let alignment = Layout::Union(union_layout)
.allocation_alignment_bytes(layout_interner, root.target_info);
let ret_stmt = rc_return_stmt(root, ident_ids, ctx);
let modify_structure_stmt = modify_refcount(
root,
@ -1453,7 +1589,7 @@ fn refcount_union_tailrec<'a>(
)
};
let alignment = layout_interner.alignment_bytes(layout);
let alignment = layout_interner.allocation_alignment_bytes(layout);
let modify_structure_stmt = modify_refcount(
root,
ident_ids,
@ -1501,7 +1637,7 @@ fn refcount_union_tailrec<'a>(
let mut tail_stmt = None;
for (i, field) in field_layouts.iter().enumerate() {
if i != tailrec_index {
filtered.push(*field);
filtered.push((i, *field));
} else {
let field_val =
root.create_symbol(ident_ids, &format!("field_{}_{}", tag_id, i));
@ -1535,7 +1671,14 @@ fn refcount_union_tailrec<'a>(
)),
));
(*field_layouts, tail_stmt)
let field_layouts = field_layouts
.iter()
.copied()
.enumerate()
.collect_in::<Vec<_>>(root.arena)
.into_bump_slice();
(field_layouts, tail_stmt)
};
let fields_stmt = refcount_tag_fields(
@ -1606,20 +1749,20 @@ fn refcount_tag_fields<'a>(
ctx: &mut Context<'a>,
layout_interner: &mut STLayoutInterner<'a>,
union_layout: UnionLayout<'a>,
field_layouts: &'a [InLayout<'a>],
field_layouts: &'a [(usize, InLayout<'a>)],
structure: Symbol,
tag_id: TagIdIntType,
following: Stmt<'a>,
) -> Stmt<'a> {
let mut stmt = following;
for (i, field_layout) in field_layouts.iter().enumerate().rev() {
for (i, field_layout) in field_layouts.iter().rev() {
if layout_interner.contains_refcounted(*field_layout) {
let field_val = root.create_symbol(ident_ids, &format!("field_{}_{}", tag_id, i));
let field_val_expr = Expr::UnionAtIndex {
union_layout,
tag_id,
index: i as u64,
index: *i as u64,
structure,
};
let field_val_stmt = |next| Stmt::Let(field_val, field_val_expr, *field_layout, next);
@ -1662,7 +1805,7 @@ fn refcount_boxed<'a>(
//
let rc_ptr = root.create_symbol(ident_ids, "rc_ptr");
let alignment = layout_interner.alignment_bytes(layout);
let alignment = layout_interner.allocation_alignment_bytes(layout);
let ret_stmt = rc_return_stmt(root, ident_ids, ctx);
let modify_outer = modify_refcount(
root,

View file

@ -474,6 +474,10 @@ impl<'a, 'r> Ctx<'a, 'r> {
&Expr::Reset {
symbol,
update_mode: _,
}
| &Expr::ResetRef {
symbol,
update_mode: _,
} => {
self.check_sym_exists(symbol);
None

File diff suppressed because it is too large Load diff

View file

@ -5,7 +5,7 @@ use crate::ir::literal::{make_num_literal, IntOrFloatValue};
use crate::layout::{
self, Builtin, ClosureCallOptions, ClosureRepresentation, EnumDispatch, InLayout, LambdaName,
LambdaSet, Layout, LayoutCache, LayoutInterner, LayoutProblem, Niche, RawFunctionLayout,
STLayoutInterner, TLLayoutInterner, TagIdIntType, UnionLayout, WrappedVariant,
TLLayoutInterner, TagIdIntType, UnionLayout, WrappedVariant,
};
use bumpalo::collections::{CollectIn, Vec};
use bumpalo::Bump;
@ -308,7 +308,6 @@ pub struct Proc<'a> {
pub closure_data_layout: Option<InLayout<'a>>,
pub ret_layout: InLayout<'a>,
pub is_self_recursive: SelfRecursive,
pub must_own_arguments: bool,
pub host_exposed_layouts: HostExposedLayouts<'a>,
}
@ -408,50 +407,6 @@ impl<'a> Proc<'a> {
String::from_utf8(w).unwrap()
}
pub fn insert_refcount_operations<'i>(
arena: &'a Bump,
layout_interner: &'i STLayoutInterner<'a>,
home: ModuleId,
ident_ids: &'i mut IdentIds,
update_mode_ids: &'i mut UpdateModeIds,
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
host_exposed_procs: &[Symbol],
) {
let borrow_params =
crate::borrow::infer_borrow(arena, layout_interner, procs, host_exposed_procs);
crate::inc_dec::visit_procs(
arena,
layout_interner,
home,
ident_ids,
update_mode_ids,
arena.alloc(borrow_params),
procs,
);
}
pub fn insert_reset_reuse_operations<'i>(
arena: &'a Bump,
layout_interner: &'i mut STLayoutInterner<'a>,
home: ModuleId,
ident_ids: &'i mut IdentIds,
update_mode_ids: &'i mut UpdateModeIds,
procs: &mut MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) {
for proc in procs.values_mut() {
let new_proc = crate::reset_reuse::insert_reset_reuse(
arena,
layout_interner,
home,
ident_ids,
update_mode_ids,
proc.clone(),
);
*proc = new_proc;
}
}
fn make_tail_recursive(&mut self, env: &mut Env<'a, '_>) {
let mut args = Vec::with_capacity_in(self.args.len(), env.arena);
let mut proc_args = Vec::with_capacity_in(self.args.len(), env.arena);
@ -1926,6 +1881,13 @@ pub enum Expr<'a> {
update_mode: UpdateModeId,
},
// Just like Reset, but does not recursively decrement the children.
// Used in reuse analysis to replace a decref with a resetRef to avoid decrementing when the dec ref didn't.
ResetRef {
symbol: Symbol,
update_mode: UpdateModeId,
},
RuntimeErrorFunction(&'a str),
}
@ -2045,11 +2007,21 @@ impl<'a> Expr<'a> {
Reset {
symbol,
update_mode,
} => alloc.text(format!(
"Reset {{ symbol: {:?}, id: {} }}",
symbol, update_mode.id
)),
} => alloc
.text("Reset { symbol: ")
.append(symbol_to_doc(alloc, *symbol, pretty))
.append(", id: ")
.append(format!("{:?}", update_mode))
.append(" }"),
ResetRef {
symbol,
update_mode,
} => alloc
.text("ResetRef { symbol: ")
.append(symbol_to_doc(alloc, *symbol, pretty))
.append(", id: ")
.append(format!("{:?}", update_mode))
.append(" }"),
Struct(args) => {
let it = args.iter().map(|s| symbol_to_doc(alloc, *s, pretty));
@ -3202,7 +3174,6 @@ fn generate_runtime_error_function<'a>(
closure_data_layout: None,
ret_layout,
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: false,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
}
}
@ -3295,7 +3266,6 @@ fn generate_host_exposed_function<'a>(
closure_data_layout: None,
ret_layout: result,
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: false,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
};
@ -3360,7 +3330,6 @@ fn generate_host_exposed_lambda_set<'a>(
closure_data_layout: None,
ret_layout: return_layout,
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: false,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
};
@ -3457,7 +3426,6 @@ fn specialize_proc_help<'a>(
closure_data_layout: Some(closure_data_layout),
ret_layout,
is_self_recursive: recursivity,
must_own_arguments: false,
host_exposed_layouts,
}
}
@ -3657,7 +3625,6 @@ fn specialize_proc_help<'a>(
closure_data_layout,
ret_layout,
is_self_recursive: recursivity,
must_own_arguments: false,
host_exposed_layouts,
}
}
@ -5049,9 +5016,12 @@ pub fn with_hole<'a>(
);
}
CopyExisting(index) => {
let record_needs_specialization =
procs.ability_member_aliases.get(structure).is_some();
let specialized_structure_sym = if record_needs_specialization {
let structure_needs_specialization =
procs.ability_member_aliases.get(structure).is_some()
|| procs.is_module_thunk(structure)
|| procs.is_imported_module_thunk(structure);
let specialized_structure_sym = if structure_needs_specialization {
// We need to specialize the record now; create a new one for it.
// TODO: reuse this symbol for all updates
env.unique_symbol()
@ -5068,10 +5038,7 @@ pub fn with_hole<'a>(
stmt =
Stmt::Let(*symbol, access_expr, *field_layout, arena.alloc(stmt));
// If the records needs specialization or it's a thunk, we need to
// create the specialized definition or force the thunk, respectively.
// Both cases are handled below.
if record_needs_specialization || procs.is_module_thunk(structure) {
if structure_needs_specialization {
stmt = specialize_symbol(
env,
procs,
@ -5500,7 +5467,7 @@ pub fn with_hole<'a>(
let passed_function = PassedFunction {
name: lambda_name,
captured_environment: closure_data_symbol,
owns_captured_environment: false,
owns_captured_environment: true,
specialization_id,
argument_layouts: arg_layouts,
return_layout: ret_layout,
@ -7501,7 +7468,9 @@ fn substitute_in_expr<'a>(
NullPointer => None,
Reuse { .. } | Reset { .. } => unreachable!("reset/reuse have not been introduced yet"),
Reuse { .. } | Reset { .. } | ResetRef { .. } => {
unreachable!("reset/resetref/reuse have not been introduced yet")
}
Struct(args) => {
let mut did_change = false;
@ -9764,7 +9733,6 @@ where
closure_data_layout: None,
ret_layout: *field,
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: false,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
};
@ -9860,7 +9828,6 @@ where
closure_data_layout: None,
ret_layout: *field,
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: false,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
};

View file

@ -2661,9 +2661,10 @@ impl<'a> Layout<'a> {
Layout::RecursivePointer(_) => {
unreachable!("should be looked up to get an actual layout")
}
Layout::Boxed(inner) => interner
.get(*inner)
.allocation_alignment_bytes(interner, target_info),
Layout::Boxed(inner) => Ord::max(
ptr_width,
interner.get(*inner).alignment_bytes(interner, target_info),
),
}
}

File diff suppressed because it is too large Load diff

View file

@ -3609,7 +3609,7 @@ fn check_for_infinite_type(
}
Content::LambdaSet(subs::LambdaSet {
solved,
recursion_var: _,
recursion_var: OptVariable::NONE,
unspecialized,
ambient_function: ambient_function_var,
}) => {

View file

@ -31,7 +31,7 @@ mod solve_expr {
..
},
src,
) = run_load_and_infer(src, false)?;
) = run_load_and_infer(src, [], false)?;
let mut can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();
@ -103,7 +103,7 @@ mod solve_expr {
interns,
abilities_store,
..
} = run_load_and_infer(src, false).unwrap().0;
} = run_load_and_infer(src, [], false).unwrap().0;
let can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();

View file

@ -69,5 +69,9 @@ gen-wasm = []
name = "list_map"
harness = false
[[bench]]
name = "quicksort"
harness = false
[package.metadata.cargo-udeps.ignore]
development = ["roc_wasm_interp"]
development = ["roc_wasm_interp"]

View file

@ -10,11 +10,16 @@ use roc_gen_llvm::{llvm::build::LlvmBackendMode, run_roc::RocCallResult, run_roc
use roc_mono::ir::OptLevel;
use roc_std::RocList;
// results July 6th, 2022
// results July 6, 2022
//
// roc sum map time: [612.73 ns 614.24 ns 615.98 ns]
// roc sum map_with_index time: [5.3177 us 5.3218 us 5.3255 us]
// rust (debug) time: [24.081 us 24.163 us 24.268 us]
//
// results April 9, 2023
//
// roc sum map time: [510.77 ns 517.47 ns 524.47 ns]
// roc sum map_with_index time: [573.49 ns 578.17 ns 583.76 ns]
type Input = RocList<i64>;
type Output = i64;
@ -83,6 +88,9 @@ pub fn criterion_benchmark(c: &mut Criterion) {
b.iter(|| unsafe {
let mut main_result = RocCallResult::default();
// the roc code will dec this list, so inc it first so it is not free'd
std::mem::forget(input.clone());
list_map_main(black_box(input), &mut main_result);
})
});
@ -91,6 +99,9 @@ pub fn criterion_benchmark(c: &mut Criterion) {
b.iter(|| unsafe {
let mut main_result = RocCallResult::default();
// the roc code will dec this list, so inc it first so it is not free'd
std::mem::forget(input.clone());
list_map_with_index_main(black_box(input), &mut main_result);
})
});

View file

@ -0,0 +1,158 @@
#[path = "../src/helpers/mod.rs"]
mod helpers;
// defines roc_alloc and friends
pub use helpers::platform_functions::*;
use bumpalo::Bump;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use roc_gen_llvm::{llvm::build::LlvmBackendMode, run_roc::RocCallResult, run_roc_dylib};
use roc_mono::ir::OptLevel;
use roc_std::RocList;
// results April 9, 2023
//
// > pure roc quicksort time: [106.97 us 107.27 us 107.63 us]
// > roc zig quicksort time: [34.765 us 35.301 us 35.865 us]
// > rust std sort time: [20.413 us 20.623 us 20.838 us]
type Input = RocList<i64>;
type Output = RocList<i64>;
type Main<I, O> = unsafe extern "C" fn(I, *mut RocCallResult<O>);
const ZIG_ROC_QUICKSORT: &str = indoc::indoc!(
r#"
app "bench" provides [main] to "./platform"
main : List I64 -> List I64
main = \originalList -> List.sortAsc originalList
"#
);
const PURE_ROC_QUICKSORT: &str = indoc::indoc!(
r#"
app "bench" provides [main] to "./platform"
main : List I64 -> List I64
main = \originalList ->
n = List.len originalList
quicksortHelp originalList 0 (n - 1)
quicksortHelp : List (Num a), Nat, Nat -> List (Num a)
quicksortHelp = \list, low, high ->
if low < high then
when partition low high list is
Pair partitionIndex partitioned ->
partitioned
|> quicksortHelp low (partitionIndex - 1)
|> quicksortHelp (partitionIndex + 1) high
else
list
partition : Nat, Nat, List (Num a) -> [Pair Nat (List (Num a))]
partition = \low, high, initialList ->
when List.get initialList high is
Ok pivot ->
when partitionHelp low low initialList high pivot is
Pair newI newList ->
Pair newI (List.swap newList newI high)
Err _ ->
Pair low initialList
partitionHelp : Nat, Nat, List (Num c), Nat, Num c -> [Pair Nat (List (Num c))]
partitionHelp = \i, j, list, high, pivot ->
if j < high then
when List.get list j is
Ok value ->
if value <= pivot then
partitionHelp (i + 1) (j + 1) (List.swap list i j) high pivot
else
partitionHelp i (j + 1) list high pivot
Err _ ->
Pair i list
else
Pair i list
"#
);
fn roc_function<'a>(
arena: &'a Bump,
source: &str,
) -> libloading::Symbol<'a, Main<*mut Input, Output>> {
let config = helpers::llvm::HelperConfig {
mode: LlvmBackendMode::GenTest,
ignore_problems: false,
add_debug_info: true,
opt_level: OptLevel::Optimize,
};
let context = inkwell::context::Context::create();
let (main_fn_name, errors, lib) =
helpers::llvm::helper(arena, config, source, arena.alloc(context));
assert!(errors.is_empty(), "Encountered errors:\n{}", errors);
run_roc_dylib!(arena.alloc(lib), main_fn_name, *mut Input, Output)
}
pub fn criterion_benchmark(c: &mut Criterion) {
let arena = Bump::new();
let pure_roc_quicksort_main = roc_function(&arena, PURE_ROC_QUICKSORT);
let roc_zig_quicksort_main = roc_function(&arena, ZIG_ROC_QUICKSORT);
let input_numbers: Vec<_> = std::iter::repeat([1, 2, 3, 4, 5, 6, 7, 8])
.flatten()
.take(1000)
.collect();
let input = arena.alloc(RocList::from_slice(&input_numbers));
c.bench_function("pure roc quicksort", |b| {
b.iter(|| unsafe {
let mut main_result = RocCallResult::default();
assert!(input.is_unique());
// reset_input
input.copy_from_slice(&input_numbers);
pure_roc_quicksort_main(black_box(input), &mut main_result);
})
});
c.bench_function("roc zig quicksort", |b| {
b.iter(|| unsafe {
let mut main_result = RocCallResult::default();
assert!(input.is_unique());
// reset_input
input.copy_from_slice(&input_numbers);
roc_zig_quicksort_main(black_box(input), &mut main_result);
})
});
c.bench_function("rust std sort", |b| {
b.iter(|| unsafe {
assert!(input.is_unique());
// reset_input
input.copy_from_slice(&input_numbers);
// an attempt to block optimizing based on the input list
let ptr = black_box(input.as_mut_ptr());
let input = std::slice::from_raw_parts_mut(ptr, 1000);
input.sort()
})
});
}
criterion_group!(quicksort_benches, criterion_benchmark);
criterion_main!(quicksort_benches);

View file

@ -2257,7 +2257,7 @@ fn gen_wrap_len() {
"#
),
RocList::from_slice(&[3]),
RocList<i64>
RocList<usize>
);
}

View file

@ -496,3 +496,78 @@ fn boxed_str_dec() {
]
);
}
#[test]
#[cfg(any(feature = "gen-wasm"))]
fn non_nullable_unwrapped_alignment_8() {
assert_refcounts!(
indoc!(
r#"
Expr : [ZAdd Expr Expr, Val I64, Var I64]
eval : Expr -> I64
eval = \e ->
when e is
Var _ -> 0
Val v -> v
ZAdd l r -> eval l + eval r
expr : Expr
expr = (ZAdd (Val 4) (Val 5))
eval expr
"#
),
i64,
&[
Deallocated, // Val 4
Deallocated, // Val 5
Deallocated, // ZAdd _ _
]
);
}
#[test]
#[cfg(any(feature = "gen-wasm"))]
fn reset_reuse_alignment_8() {
assert_refcounts!(
indoc!(
r#"
app "test" provides [main] to "./platform"
Expr : [ZAdd Expr Expr, Val I64, Var I64]
eval : Expr -> I64
eval = \e ->
when e is
Var _ -> 0
Val v -> v
ZAdd l r -> eval l + eval r
constFolding : Expr -> Expr
constFolding = \e ->
when e is
ZAdd e1 e2 ->
when Pair e1 e2 is
Pair (Val a) (Val b) -> Val (a+b)
Pair _ _ -> ZAdd e1 e2
_ -> e
expr : Expr
expr = ZAdd (Val 4) (Val 5)
main : I64
main = eval (constFolding expr)
"#
),
i64,
&[
Deallocated, // Val 4
Deallocated, // Val 5
Deallocated, // ZAdd _ _
]
);
}

View file

@ -116,7 +116,6 @@ fn build_app_mono<'a>(
closure_data_layout: None,
ret_layout: int_layout,
is_self_recursive: SelfRecursive::NotSelfRecursive,
must_own_arguments: false,
host_exposed_layouts: HostExposedLayouts::NotHostExposed,
};

View file

@ -68,10 +68,12 @@ procedure List.80 (List.544, List.545, List.546, List.547, List.548):
let List.523 : U64 = CallByName Num.19 List.436 List.524;
jump List.518 List.433 List.438 List.435 List.523 List.437;
else
dec List.433;
let List.439 : U64 = UnionAtIndex (Id 0) (Index 0) List.521;
let List.525 : [C U64, C U64] = TagId(0) List.439;
ret List.525;
else
dec List.433;
let List.519 : [C U64, C U64] = TagId(1) List.434;
ret List.519;
in
@ -98,6 +100,7 @@ procedure Num.77 (#Attr.2, #Attr.3):
procedure Test.1 (Test.2):
let Test.13 : U64 = 0i64;
let Test.14 : {} = Struct {};
inc Test.2;
let Test.3 : U64 = CallByName List.26 Test.2 Test.13 Test.14;
let Test.12 : U64 = 0i64;
let Test.10 : Int1 = CallByName Bool.11 Test.3 Test.12;

View file

@ -7,15 +7,18 @@ procedure List.2 (List.96, List.97):
let List.504 : Int1 = CallByName Num.22 List.97 List.508;
if List.504 then
let List.506 : Str = CallByName List.66 List.96 List.97;
dec List.96;
let List.505 : [C {}, C Str] = TagId(1) List.506;
ret List.505;
else
dec List.96;
let List.503 : {} = Struct {};
let List.502 : [C {}, C Str] = TagId(0) List.503;
ret List.502;
procedure List.5 (#Attr.2, #Attr.3):
let List.510 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.10 #Attr.3;
decref #Attr.2;
ret List.510;
procedure List.6 (#Attr.2):
@ -53,13 +56,13 @@ procedure Result.5 (Result.12, Result.13):
let Result.40 : U8 = GetTagId Result.12;
let Result.41 : Int1 = lowlevel Eq Result.39 Result.40;
if Result.41 then
dec Result.13;
let Result.14 : Str = UnionAtIndex (Id 1) (Index 0) Result.12;
inc Result.14;
dec Result.12;
ret Result.14;
else
dec Result.12;
inc Result.13;
ret Result.13;
procedure Test.10 (Test.11):
@ -78,6 +81,8 @@ procedure Test.2 (Test.6):
let Test.31 : Int1 = lowlevel Eq Test.29 Test.30;
if Test.31 then
let Test.7 : [<r>C List *self, C *self] = UnionAtIndex (Id 1) (Index 0) Test.6;
inc Test.7;
dec Test.6;
let Test.8 : Str = CallByName Test.2 Test.7;
let Test.18 : Int1 = CallByName Bool.1;
if Test.18 then
@ -88,18 +93,17 @@ procedure Test.2 (Test.6):
ret Test.17;
else
let Test.9 : List [<r>C List *self, C *self] = UnionAtIndex (Id 0) (Index 0) Test.6;
inc Test.9;
dec Test.6;
let Test.24 : {} = Struct {};
let Test.23 : List Str = CallByName List.5 Test.9 Test.24;
let Test.21 : [C {}, C Str] = CallByName List.9 Test.23;
dec Test.23;
let Test.22 : Str = "foo";
let Test.20 : Str = CallByName Result.5 Test.21 Test.22;
dec Test.22;
ret Test.20;
procedure Test.0 ():
let Test.32 : List [<r>C List *self, C *self] = Array [];
let Test.15 : [<r>C List *self, C *self] = TagId(0) Test.32;
let Test.14 : Str = CallByName Test.2 Test.15;
dec Test.15;
ret Test.14;

View file

@ -28,6 +28,7 @@ procedure List.80 (List.517, List.518, List.519, List.520, List.521):
let List.505 : U64 = CallByName Num.19 List.436 List.506;
jump List.500 List.433 List.503 List.435 List.505 List.437;
else
dec List.433;
ret List.434;
in
jump List.500 List.517 List.518 List.519 List.520 List.521;
@ -65,7 +66,6 @@ procedure Test.11 (Test.53, Test.54):
case 0:
dec Test.7;
let Test.28 : Str = CallByName Test.2 Test.29;
dec Test.29;
ret Test.28;
case 1:
@ -89,7 +89,6 @@ procedure Test.11 (Test.53, Test.54):
jump Test.27 Test.53 Test.54;
procedure Test.2 (Test.13):
inc Test.13;
ret Test.13;
procedure Test.3 (Test.14):
@ -136,7 +135,6 @@ procedure Test.9 (Test.10, #Attr.12):
case 0:
dec Test.7;
let Test.39 : Str = CallByName Test.2 Test.10;
dec Test.10;
jump Test.38 Test.39;
case 1:
@ -156,14 +154,12 @@ procedure Test.0 ():
let Test.23 : Int1 = CallByName Bool.2;
let Test.22 : Int1 = CallByName Test.1 Test.23;
let Test.16 : [<rnw><null>, C *self Int1, C *self Int1] = CallByName List.18 Test.20 Test.21 Test.22;
dec Test.20;
let Test.18 : Str = "hello";
let Test.19 : U8 = GetTagId Test.16;
switch Test.19:
case 0:
dec Test.16;
let Test.17 : Str = CallByName Test.2 Test.18;
dec Test.18;
ret Test.17;
case 1:

View file

@ -7,9 +7,11 @@ procedure List.2 (List.96, List.97):
let List.496 : Int1 = CallByName Num.22 List.97 List.500;
if List.496 then
let List.498 : {} = CallByName List.66 List.96 List.97;
dec List.96;
let List.497 : [C {}, C {}] = TagId(1) List.498;
ret List.497;
else
dec List.96;
let List.495 : {} = Struct {};
let List.494 : [C {}, C {}] = TagId(0) List.495;
ret List.494;
@ -27,6 +29,7 @@ procedure Num.22 (#Attr.2, #Attr.3):
ret Num.275;
procedure Test.2 (Test.5):
dec Test.5;
let Test.17 : Str = "bar";
ret Test.17;
@ -35,7 +38,6 @@ procedure Test.0 ():
joinpoint Test.15 Test.3:
let Test.13 : U64 = 0i64;
let Test.6 : [C {}, C {}] = CallByName List.2 Test.3 Test.13;
dec Test.3;
let Test.10 : U8 = 1i64;
let Test.11 : U8 = GetTagId Test.6;
let Test.12 : Int1 = lowlevel Eq Test.10 Test.11;
@ -43,7 +45,6 @@ procedure Test.0 ():
let Test.4 : {} = UnionAtIndex (Id 1) (Index 0) Test.6;
let Test.8 : Str = "foo";
let Test.7 : Str = CallByName Test.2 Test.8;
dec Test.8;
ret Test.7;
else
let Test.9 : Str = "bad!";

View file

@ -88,7 +88,6 @@ procedure Json.162 (Json.163, Json.904, Json.161):
let Json.912 : {List U8, U64} = Struct {Json.165, Json.935};
let Json.913 : {} = Struct {};
let Json.911 : {List U8, U64} = CallByName List.18 Json.161 Json.912 Json.913;
dec Json.161;
let Json.167 : List U8 = StructAtIndex 0 Json.911;
inc Json.167;
dec Json.911;
@ -105,7 +104,6 @@ procedure Json.162 (Json.163, Json.904, Json.161):
let Json.952 : {List U8, U64} = Struct {Json.165, Json.975};
let Json.953 : {} = Struct {};
let Json.951 : {List U8, U64} = CallByName List.18 Json.161 Json.952 Json.953;
dec Json.161;
let Json.167 : List U8 = StructAtIndex 0 Json.951;
inc Json.167;
dec Json.951;
@ -268,6 +266,7 @@ procedure List.80 (List.547, List.548, List.549, List.550, List.551):
let List.527 : U64 = CallByName Num.19 List.436 List.528;
jump List.522 List.433 List.525 List.435 List.527 List.437;
else
dec List.433;
ret List.434;
in
jump List.522 List.547 List.548 List.549 List.550 List.551;
@ -282,6 +281,7 @@ procedure List.80 (List.621, List.622, List.623, List.624, List.625):
let List.600 : U64 = CallByName Num.19 List.436 List.601;
jump List.595 List.433 List.598 List.435 List.600 List.437;
else
dec List.433;
ret List.434;
in
jump List.595 List.621 List.622 List.623 List.624 List.625;

View file

@ -61,7 +61,6 @@ procedure Json.162 (Json.163, Json.904, Json.161):
let Json.912 : {List U8, U64} = Struct {Json.165, Json.935};
let Json.913 : {} = Struct {};
let Json.911 : {List U8, U64} = CallByName List.18 Json.161 Json.912 Json.913;
dec Json.161;
let Json.167 : List U8 = StructAtIndex 0 Json.911;
inc Json.167;
dec Json.911;
@ -165,6 +164,7 @@ procedure List.80 (List.554, List.555, List.556, List.557, List.558):
let List.533 : U64 = CallByName Num.19 List.436 List.534;
jump List.528 List.433 List.531 List.435 List.533 List.437;
else
dec List.433;
ret List.434;
in
jump List.528 List.554 List.555 List.556 List.557 List.558;

View file

@ -69,7 +69,6 @@ procedure Json.162 (Json.163, Json.904, Json.161):
let Json.912 : {List U8, U64} = Struct {Json.165, Json.935};
let Json.913 : {} = Struct {};
let Json.911 : {List U8, U64} = CallByName List.18 Json.161 Json.912 Json.913;
dec Json.161;
let Json.167 : List U8 = StructAtIndex 0 Json.911;
inc Json.167;
dec Json.911;
@ -173,6 +172,7 @@ procedure List.80 (List.554, List.555, List.556, List.557, List.558):
let List.533 : U64 = CallByName Num.19 List.436 List.534;
jump List.528 List.433 List.531 List.435 List.533 List.437;
else
dec List.433;
ret List.434;
in
jump List.528 List.554 List.555 List.556 List.557 List.558;

View file

@ -82,7 +82,6 @@ procedure Json.188 (Json.189, Json.904, #Attr.12):
let Json.914 : {List U8, U64} = Struct {Json.191, Json.926};
let Json.915 : {} = Struct {};
let Json.913 : {List U8, U64} = CallByName List.18 Json.187 Json.914 Json.915;
dec Json.187;
let Json.193 : List U8 = StructAtIndex 0 Json.913;
inc Json.193;
dec Json.913;
@ -174,6 +173,7 @@ procedure List.80 (List.560, List.561, List.562, List.563, List.564):
let List.539 : U64 = CallByName Num.19 List.436 List.540;
jump List.534 List.433 List.537 List.435 List.539 List.437;
else
dec List.433;
ret List.434;
in
jump List.534 List.560 List.561 List.562 List.563 List.564;

View file

@ -88,7 +88,6 @@ procedure Json.188 (Json.189, Json.904, #Attr.12):
let Json.914 : {List U8, U64} = Struct {Json.191, Json.926};
let Json.915 : {} = Struct {};
let Json.913 : {List U8, U64} = CallByName List.18 Json.187 Json.914 Json.915;
dec Json.187;
let Json.193 : List U8 = StructAtIndex 0 Json.913;
inc Json.193;
dec Json.913;
@ -180,6 +179,7 @@ procedure List.80 (List.560, List.561, List.562, List.563, List.564):
let List.539 : U64 = CallByName Num.19 List.436 List.540;
jump List.534 List.433 List.537 List.435 List.539 List.437;
else
dec List.433;
ret List.434;
in
jump List.534 List.560 List.561 List.562 List.563 List.564;

View file

@ -1,11 +1,9 @@
procedure Test.1 (Test.2, Test.3):
inc Test.2;
dec Test.3;
ret Test.2;
procedure Test.0 ():
let Test.5 : List I64 = Array [1i64, 2i64, 3i64];
let Test.6 : List I64 = Array [3i64, 2i64, 1i64];
let Test.4 : List I64 = CallByName Test.1 Test.5 Test.6;
dec Test.6;
dec Test.5;
ret Test.4;

View file

@ -9,6 +9,7 @@ procedure Bool.2 ():
procedure Test.2 (Test.4):
let Test.11 : U8 = 1i64;
let Test.12 : U8 = GetTagId Test.4;
dec Test.4;
let Test.13 : Int1 = lowlevel Eq Test.11 Test.12;
if Test.13 then
let Test.9 : Int1 = CallByName Bool.2;
@ -22,5 +23,4 @@ procedure Test.0 ():
let Test.15 : [<rnu><null>, C I64 *self] = TagId(1) ;
let Test.8 : [<rnu><null>, C I64 *self] = TagId(0) Test.14 Test.15;
let Test.7 : Int1 = CallByName Test.2 Test.8;
dec Test.8;
ret Test.7;

View file

@ -11,9 +11,11 @@ procedure List.2 (List.96, List.97):
let List.504 : Int1 = CallByName Num.22 List.97 List.508;
if List.504 then
let List.506 : I64 = CallByName List.66 List.96 List.97;
dec List.96;
let List.505 : [C {}, C I64] = TagId(1) List.506;
ret List.505;
else
dec List.96;
let List.503 : {} = Struct {};
let List.502 : [C {}, C I64] = TagId(0) List.503;
ret List.502;
@ -55,6 +57,7 @@ procedure Str.47 (#Attr.2):
procedure Str.72 (Str.244):
let Str.245 : {I64, U8} = CallByName Str.47 Str.244;
dec Str.244;
let Str.304 : U8 = StructAtIndex 1 Str.245;
let Str.305 : U8 = 0i64;
let Str.301 : Int1 = CallByName Bool.11 Str.304 Str.305;
@ -72,10 +75,8 @@ procedure Test.0 ():
if Test.3 then
let Test.5 : List I64 = Array [];
let Test.4 : [C Int1, C I64] = CallByName List.9 Test.5;
dec Test.5;
ret Test.4;
else
let Test.2 : Str = "";
let Test.1 : [C Int1, C I64] = CallByName Str.27 Test.2;
dec Test.2;
ret Test.1;

View file

@ -8,10 +8,13 @@ procedure Test.2 (Test.19):
let Test.17 : U8 = GetTagId Test.7;
let Test.18 : Int1 = lowlevel Eq Test.16 Test.17;
if Test.18 then
dec Test.7;
let Test.14 : {} = Struct {};
ret Test.14;
else
let Test.5 : [<rnu><null>, C *self] = UnionAtIndex (Id 0) (Index 0) Test.7;
inc Test.5;
dec Test.7;
jump Test.13 Test.5;
in
jump Test.13 Test.19;
@ -19,7 +22,6 @@ procedure Test.2 (Test.19):
procedure Test.0 ():
let Test.12 : [<rnu><null>, C *self] = TagId(1) ;
let Test.10 : {} = CallByName Test.2 Test.12;
dec Test.12;
let Test.11 : {} = Struct {};
let Test.8 : Int1 = CallByName Bool.11 Test.10 Test.11;
let Test.9 : Str = "";

View file

@ -16,8 +16,8 @@ procedure Test.1 (Test.2, Test.3):
let Test.23 : {} = Struct {};
joinpoint Test.24 Test.22:
let Test.20 : Int1 = CallByName Bool.11 Test.21 Test.22;
dec Test.22;
dec Test.21;
dec Test.22;
let Test.18 : Int1 = CallByName Bool.4 Test.19 Test.20;
ret Test.18;
in

View file

@ -4,8 +4,6 @@ procedure Bool.1 ():
procedure Bool.11 (#Attr.2, #Attr.3):
let Bool.23 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
dec #Attr.3;
dec #Attr.2;
ret Bool.23;
procedure Bool.11 (#Attr.2, #Attr.3):
@ -47,6 +45,7 @@ procedure Decode.27 (Decode.107, Decode.108):
let Decode.109 : [C {}, C Str] = StructAtIndex 1 Decode.122;
inc Decode.109;
dec Decode.122;
inc Decode.110;
let Decode.125 : Int1 = CallByName List.1 Decode.110;
if Decode.125 then
dec Decode.110;
@ -82,10 +81,11 @@ procedure Json.448 (Json.449, Json.904):
let Json.451 : List U8 = StructAtIndex 1 Json.1041;
inc Json.451;
dec Json.1041;
inc Json.451;
let Json.1037 : Int1 = CallByName List.1 Json.451;
if Json.1037 then
dec Json.452;
dec Json.451;
dec Json.452;
let Json.1040 : {} = Struct {};
let Json.1039 : [C {}, C Str] = TagId(0) Json.1040;
let Json.1038 : {List U8, [C {}, C Str]} = Struct {Json.449, Json.1039};
@ -112,8 +112,8 @@ procedure Json.448 (Json.449, Json.904):
let Json.905 : {List U8, [C {}, C Str]} = Struct {Json.452, Json.906};
ret Json.905;
else
dec Json.457;
dec Json.452;
dec Json.457;
let Json.909 : {} = Struct {};
let Json.908 : [C {}, C Str] = TagId(0) Json.909;
let Json.907 : {List U8, [C {}, C Str]} = Struct {Json.449, Json.908};
@ -127,6 +127,7 @@ procedure Json.55 ():
procedure Json.56 (Json.462):
let Json.1053 : [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64] = TagId(4) ;
let Json.1054 : {} = Struct {};
inc Json.462;
let Json.1042 : [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64] = CallByName List.26 Json.462 Json.1053 Json.1054;
let Json.1050 : U8 = 2i64;
let Json.1051 : U8 = GetTagId Json.1042;
@ -490,8 +491,10 @@ procedure Json.65 (Json.1160):
inc Json.522;
dec Json.899;
let Json.1035 : U64 = 0i64;
inc Json.521;
let Json.523 : [C {}, C U8] = CallByName List.2 Json.521 Json.1035;
let Json.1034 : U64 = 1i64;
inc Json.521;
let Json.524 : [C {}, C U8] = CallByName List.2 Json.521 Json.1034;
let Json.1033 : U64 = 2i64;
inc Json.521;
@ -591,6 +594,7 @@ procedure Json.65 (Json.1160):
procedure List.1 (List.95):
let List.552 : U64 = CallByName List.6 List.95;
dec List.95;
let List.553 : U64 = 0i64;
let List.551 : Int1 = CallByName Bool.11 List.552 List.553;
ret List.551;
@ -600,9 +604,11 @@ procedure List.2 (List.96, List.97):
let List.547 : Int1 = CallByName Num.22 List.97 List.550;
if List.547 then
let List.549 : U8 = CallByName List.66 List.96 List.97;
dec List.96;
let List.548 : [C {}, C U8] = TagId(1) List.549;
ret List.548;
else
dec List.96;
let List.546 : {} = Struct {};
let List.545 : [C {}, C U8] = TagId(0) List.546;
ret List.545;
@ -707,10 +713,12 @@ procedure List.80 (List.612, List.613, List.614, List.615, List.616):
let List.575 : U64 = CallByName Num.19 List.436 List.576;
jump List.570 List.433 List.438 List.435 List.575 List.437;
else
dec List.433;
let List.439 : [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64] = UnionAtIndex (Id 0) (Index 0) List.573;
let List.577 : [C [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64], C [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64]] = TagId(0) List.439;
ret List.577;
else
dec List.433;
let List.571 : [C [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64], C [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64]] = TagId(1) List.434;
ret List.571;
in
@ -788,8 +796,8 @@ procedure Test.3 ():
let Test.1 : [C [C List U8, C ], C Str] = CallByName Decode.27 Test.0 Test.8;
let Test.7 : Str = "Roc";
let Test.6 : [C [C List U8, C ], C Str] = TagId(1) Test.7;
inc Test.1;
let Test.5 : Int1 = CallByName Bool.11 Test.1 Test.6;
dec Test.6;
expect Test.5;
let Test.4 : {} = Struct {};
ret Test.4;

View file

@ -4,8 +4,6 @@ procedure Bool.1 ():
procedure Bool.11 (#Attr.2, #Attr.3):
let Bool.23 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
dec #Attr.3;
dec #Attr.2;
ret Bool.23;
procedure Bool.11 (#Attr.2, #Attr.3):
@ -52,10 +50,11 @@ procedure Json.448 (Json.449, Json.904):
let Json.451 : List U8 = StructAtIndex 1 Json.1041;
inc Json.451;
dec Json.1041;
inc Json.451;
let Json.1037 : Int1 = CallByName List.1 Json.451;
if Json.1037 then
dec Json.452;
dec Json.451;
dec Json.452;
let Json.1040 : {} = Struct {};
let Json.1039 : [C {}, C Str] = TagId(0) Json.1040;
let Json.1038 : {List U8, [C {}, C Str]} = Struct {Json.449, Json.1039};
@ -82,8 +81,8 @@ procedure Json.448 (Json.449, Json.904):
let Json.905 : {List U8, [C {}, C Str]} = Struct {Json.452, Json.906};
ret Json.905;
else
dec Json.457;
dec Json.452;
dec Json.457;
let Json.909 : {} = Struct {};
let Json.908 : [C {}, C Str] = TagId(0) Json.909;
let Json.907 : {List U8, [C {}, C Str]} = Struct {Json.449, Json.908};
@ -97,6 +96,7 @@ procedure Json.55 ():
procedure Json.56 (Json.462):
let Json.1053 : [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64] = TagId(4) ;
let Json.1054 : {} = Struct {};
inc Json.462;
let Json.1042 : [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64] = CallByName List.26 Json.462 Json.1053 Json.1054;
let Json.1050 : U8 = 2i64;
let Json.1051 : U8 = GetTagId Json.1042;
@ -460,8 +460,10 @@ procedure Json.65 (Json.1160):
inc Json.522;
dec Json.899;
let Json.1035 : U64 = 0i64;
inc Json.521;
let Json.523 : [C {}, C U8] = CallByName List.2 Json.521 Json.1035;
let Json.1034 : U64 = 1i64;
inc Json.521;
let Json.524 : [C {}, C U8] = CallByName List.2 Json.521 Json.1034;
let Json.1033 : U64 = 2i64;
inc Json.521;
@ -561,6 +563,7 @@ procedure Json.65 (Json.1160):
procedure List.1 (List.95):
let List.546 : U64 = CallByName List.6 List.95;
dec List.95;
let List.547 : U64 = 0i64;
let List.545 : Int1 = CallByName Bool.11 List.546 List.547;
ret List.545;
@ -570,9 +573,11 @@ procedure List.2 (List.96, List.97):
let List.541 : Int1 = CallByName Num.22 List.97 List.544;
if List.541 then
let List.543 : U8 = CallByName List.66 List.96 List.97;
dec List.96;
let List.542 : [C {}, C U8] = TagId(1) List.543;
ret List.542;
else
dec List.96;
let List.540 : {} = Struct {};
let List.539 : [C {}, C U8] = TagId(0) List.540;
ret List.539;
@ -677,10 +682,12 @@ procedure List.80 (List.606, List.607, List.608, List.609, List.610):
let List.569 : U64 = CallByName Num.19 List.436 List.570;
jump List.564 List.433 List.438 List.435 List.569 List.437;
else
dec List.433;
let List.439 : [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64] = UnionAtIndex (Id 0) (Index 0) List.567;
let List.571 : [C [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64], C [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64]] = TagId(0) List.439;
ret List.571;
else
dec List.433;
let List.565 : [C [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64], C [C U64, C U64, C U64, C , C , C U64, C U64, C U64, C U64]] = TagId(1) List.434;
ret List.565;
in
@ -746,6 +753,7 @@ procedure Str.48 (#Attr.2, #Attr.3, #Attr.4):
procedure Str.72 (Str.244):
let Str.245 : {I64, U8} = CallByName Str.47 Str.244;
dec Str.244;
let Str.304 : U8 = StructAtIndex 1 Str.245;
let Str.305 : U8 = 0i64;
let Str.301 : Int1 = CallByName Bool.11 Str.304 Str.305;
@ -795,7 +803,6 @@ procedure Test.0 ():
inc Test.3;
dec Test.1;
let Test.19 : [C {}, C I64] = CallByName Str.27 Test.3;
dec Test.3;
let Test.25 : U8 = 1i64;
let Test.26 : U8 = GetTagId Test.19;
let Test.27 : Int1 = lowlevel Eq Test.25 Test.26;
@ -822,8 +829,8 @@ procedure Test.12 ():
let Test.18 : I64 = -1234i64;
let Test.16 : {List U8, I64} = Struct {Test.17, Test.18};
let Test.15 : [C Str, C {List U8, I64}] = TagId(1) Test.16;
inc Test.10;
let Test.14 : Int1 = CallByName Bool.11 Test.10 Test.15;
dec Test.15;
expect Test.14;
let Test.13 : {} = Struct {};
ret Test.13;

View file

@ -22,11 +22,11 @@ procedure Test.2 (Test.7, Test.8):
ret Test.41;
procedure Test.3 (Test.17):
dec Test.17;
let Test.33 : {} = Struct {};
ret Test.33;
procedure Test.4 (Test.18):
inc Test.18;
ret Test.18;
procedure Test.9 (Test.26, #Attr.12):
@ -35,7 +35,6 @@ procedure Test.9 (Test.26, #Attr.12):
let Test.46 : {} = Struct {};
let Test.45 : Str = CallByName Test.16 Test.46;
let Test.42 : Str = CallByName Test.4 Test.45;
dec Test.45;
let Test.44 : {} = Struct {};
let Test.43 : Str = CallByName Test.13 Test.44 Test.42;
ret Test.43;
@ -46,7 +45,6 @@ procedure Test.9 (Test.26, #Attr.12):
let Test.32 : {} = Struct {};
let Test.31 : Str = CallByName Test.15 Test.32;
let Test.28 : {} = CallByName Test.3 Test.31;
dec Test.31;
let Test.30 : {} = Struct {};
let Test.29 : Str = CallByName Test.11 Test.30;
ret Test.29;

View file

@ -1,5 +1,4 @@
procedure Test.1 (Test.4):
inc Test.4;
ret Test.4;
procedure Test.21 (Test.23, #Attr.12):
@ -21,10 +20,8 @@ procedure Test.0 ():
if Test.20 then
let Test.15 : Str = "";
let Test.10 : Str = CallByName Test.1 Test.15;
dec Test.15;
jump Test.9 Test.10;
else
let Test.18 : Str = "";
let Test.16 : Str = CallByName Test.1 Test.18;
dec Test.18;
jump Test.9 Test.16;

View file

@ -24,6 +24,7 @@ procedure List.80 (List.517, List.518, List.519, List.520, List.521):
let List.505 : U64 = CallByName Num.19 List.436 List.506;
jump List.500 List.433 List.503 List.435 List.505 List.437;
else
dec List.433;
ret List.434;
in
jump List.500 List.517 List.518 List.519 List.520 List.521;
@ -52,5 +53,4 @@ procedure Test.0 ():
let Test.8 : List [<rnu>C *self, <null>] = Array [];
let Test.15 : {} = Struct {};
let Test.9 : [<rnu><null>, C {[<rnu>C *self, <null>], *self}] = CallByName List.18 Test.8 Test.6 Test.15;
dec Test.8;
ret Test.9;

View file

@ -3,9 +3,11 @@ procedure List.2 (List.96, List.97):
let List.496 : Int1 = CallByName Num.22 List.97 List.500;
if List.496 then
let List.498 : I64 = CallByName List.66 List.96 List.97;
dec List.96;
let List.497 : [C {}, C I64] = TagId(1) List.498;
ret List.497;
else
dec List.96;
let List.495 : {} = Struct {};
let List.494 : [C {}, C I64] = TagId(0) List.495;
ret List.494;
@ -26,7 +28,6 @@ procedure Test.1 (Test.2):
let Test.6 : List I64 = Array [1i64, 2i64, 3i64];
let Test.7 : U64 = 0i64;
let Test.5 : [C {}, C I64] = CallByName List.2 Test.6 Test.7;
dec Test.6;
ret Test.5;
procedure Test.0 ():

View file

@ -3,15 +3,18 @@ procedure List.2 (List.96, List.97):
let List.496 : Int1 = CallByName Num.22 List.97 List.500;
if List.496 then
let List.498 : Str = CallByName List.66 List.96 List.97;
dec List.96;
let List.497 : [C {}, C Str] = TagId(1) List.498;
ret List.497;
else
dec List.96;
let List.495 : {} = Struct {};
let List.494 : [C {}, C Str] = TagId(0) List.495;
ret List.494;
procedure List.5 (#Attr.2, #Attr.3):
let List.502 : List Str = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.3 #Attr.3;
decref #Attr.2;
ret List.502;
procedure List.6 (#Attr.2):
@ -46,19 +49,18 @@ procedure Test.2 ():
let Test.15 : List Str = CallByName Test.1;
let Test.16 : {} = Struct {};
let Test.14 : List Str = CallByName List.5 Test.15 Test.16;
dec Test.15;
ret Test.14;
procedure Test.3 (Test.4):
let Test.18 : U64 = 2i64;
let Test.17 : Str = CallByName Str.16 Test.4 Test.18;
dec Test.4;
ret Test.17;
procedure Test.0 ():
let Test.12 : List Str = CallByName Test.2;
let Test.13 : U64 = 0i64;
let Test.6 : [C {}, C Str] = CallByName List.2 Test.12 Test.13;
dec Test.12;
let Test.9 : U8 = 1i64;
let Test.10 : U8 = GetTagId Test.6;
let Test.11 : Int1 = lowlevel Eq Test.9 Test.10;

View file

@ -3,9 +3,11 @@ procedure List.2 (List.96, List.97):
let List.496 : Int1 = CallByName Num.22 List.97 List.500;
if List.496 then
let List.498 : Str = CallByName List.66 List.96 List.97;
dec List.96;
let List.497 : [C {}, C Str] = TagId(1) List.498;
ret List.497;
else
dec List.96;
let List.495 : {} = Struct {};
let List.494 : [C {}, C Str] = TagId(0) List.495;
ret List.494;
@ -55,7 +57,6 @@ procedure Test.0 ():
let Test.12 : List Str = CallByName Test.2;
let Test.13 : U64 = 0i64;
let Test.6 : [C {}, C Str] = CallByName List.2 Test.12 Test.13;
dec Test.12;
let Test.9 : U8 = 1i64;
let Test.10 : U8 = GetTagId Test.6;
let Test.11 : Int1 = lowlevel Eq Test.9 Test.10;

View file

@ -1,7 +1,6 @@
procedure List.5 (#Attr.2, #Attr.3):
let List.495 : U8 = GetTagId #Attr.3;
joinpoint List.496 List.494:
inc List.494;
ret List.494;
in
switch List.495:
@ -58,8 +57,8 @@ procedure Test.0 ():
else
let Test.20 : Str = "B";
let Test.21 : Int1 = lowlevel Eq Test.20 Test.12;
dec Test.12;
dec Test.20;
dec Test.12;
if Test.21 then
let Test.16 : [C U8, C U8, C ] = TagId(1) Test.2;
jump Test.13 Test.16;

View file

@ -1,11 +1,6 @@
procedure List.28 (#Attr.2, #Attr.3):
let List.496 : List I64 = lowlevel ListSortWith { xs: `#Attr.#arg1` } #Attr.2 Num.46 #Attr.3;
let #Derived_gen.0 : Int1 = lowlevel ListIsUnique #Attr.2;
if #Derived_gen.0 then
ret List.496;
else
decref #Attr.2;
ret List.496;
ret List.496;
procedure List.59 (List.282):
let List.495 : {} = Struct {};

View file

@ -7,6 +7,8 @@ procedure Test.1 (Test.3):
ret Test.13;
procedure Test.2 (Test.4, Test.5):
dec Test.5;
dec Test.4;
let Test.9 : U64 = 18i64;
ret Test.9;
@ -16,6 +18,4 @@ procedure Test.0 ():
let Test.10 : {} = Struct {};
let Test.8 : List U16 = CallByName Test.1 Test.10;
let Test.6 : U64 = CallByName Test.2 Test.7 Test.8;
dec Test.8;
dec Test.7;
ret Test.6;

View file

@ -4,6 +4,7 @@ procedure Str.3 (#Attr.2, #Attr.3):
procedure Test.2 (Test.4):
let Test.16 : U8 = GetTagId Test.4;
dec Test.4;
switch Test.16:
case 0:
let Test.13 : Str = "A";
@ -22,15 +23,12 @@ procedure Test.0 ():
let Test.21 : [<rnw>C *self, <null>, C ] = TagId(1) ;
let Test.20 : [<rnw>C *self, <null>, C ] = TagId(0) Test.21;
let Test.17 : Str = CallByName Test.2 Test.20;
dec Test.20;
let Test.19 : [<rnw>C *self, <null>, C ] = TagId(1) ;
let Test.18 : Str = CallByName Test.2 Test.19;
dec Test.19;
let Test.10 : Str = CallByName Str.3 Test.17 Test.18;
dec Test.18;
let Test.12 : [<rnw>C *self, <null>, C ] = TagId(2) ;
let Test.11 : Str = CallByName Test.2 Test.12;
dec Test.12;
let Test.9 : Str = CallByName Str.3 Test.10 Test.11;
dec Test.11;
ret Test.9;

View file

@ -4,6 +4,7 @@ procedure Bool.11 (#Attr.2, #Attr.3):
procedure Test.2 (Test.5):
let Test.14 : U8 = GetTagId Test.5;
dec Test.5;
switch Test.14:
case 2:
let Test.11 : Str = "a";
@ -21,9 +22,8 @@ procedure Test.2 (Test.5):
procedure Test.0 ():
let Test.10 : [<rnw><null>, C Str, C *self] = TagId(0) ;
let Test.8 : Str = CallByName Test.2 Test.10;
dec Test.10;
let Test.9 : Str = "c";
let Test.7 : Int1 = CallByName Bool.11 Test.8 Test.9;
dec Test.9;
dec Test.8;
dec Test.9;
ret Test.7;

View file

@ -4,8 +4,6 @@ procedure Bool.1 ():
procedure Bool.11 (#Attr.2, #Attr.3):
let Bool.23 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
dec #Attr.3;
dec #Attr.2;
ret Bool.23;
procedure Test.1 ():
@ -30,6 +28,8 @@ procedure Test.0 ():
dec Test.5;
let Test.7 : {I64, Str} = CallByName Test.1;
let Test.6 : Int1 = CallByName Bool.11 Test.7 Test.13;
dec Test.13;
dec Test.7;
ret Test.6;
else
dec Test.5;

View file

@ -4,8 +4,6 @@ procedure Bool.1 ():
procedure Bool.11 (#Attr.2, #Attr.3):
let Bool.23 : Int1 = lowlevel Eq #Attr.2 #Attr.3;
dec #Attr.3;
dec #Attr.2;
ret Bool.23;
procedure Test.1 ():
@ -22,6 +20,8 @@ procedure Test.0 ():
if Test.13 then
let Test.6 : {I64, Str} = CallByName Test.1;
let Test.5 : Int1 = CallByName Bool.11 Test.6 Test.4;
dec Test.6;
dec Test.4;
ret Test.5;
else
dec Test.4;

View file

@ -3,9 +3,11 @@ procedure List.2 (List.96, List.97):
let List.513 : Int1 = CallByName Num.22 List.97 List.516;
if List.513 then
let List.515 : I64 = CallByName List.66 List.96 List.97;
dec List.96;
let List.514 : [C {}, C I64] = TagId(1) List.515;
ret List.514;
else
dec List.96;
let List.512 : {} = Struct {};
let List.511 : [C {}, C I64] = TagId(0) List.512;
ret List.511;
@ -45,8 +47,10 @@ procedure Num.22 (#Attr.2, #Attr.3):
procedure Test.1 (Test.2):
let Test.28 : U64 = 0i64;
inc Test.2;
let Test.26 : [C {}, C I64] = CallByName List.2 Test.2 Test.28;
let Test.27 : U64 = 0i64;
inc Test.2;
let Test.25 : [C {}, C I64] = CallByName List.2 Test.2 Test.27;
let Test.8 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.25, Test.26};
joinpoint Test.22:

View file

@ -1,18 +1,21 @@
procedure List.5 (#Attr.2, #Attr.3):
let List.494 : List [<rnnu>C List *self] = lowlevel ListMap { xs: `#Attr.#arg1` } #Attr.2 Test.2 #Attr.3;
decref #Attr.2;
ret List.494;
procedure Test.2 (Test.5):
let Test.6 : List [<rnnu>C List *self] = UnionAtIndex (Id 0) (Index 0) Test.5;
inc Test.6;
let #Derived_gen.1 : [<rnnu>C List *self] = Reset { symbol: Test.5, id: UpdateModeId { id: 1 } };
let Test.15 : {} = Struct {};
let Test.7 : List [<rnnu>C List *self] = CallByName List.5 Test.6 Test.15;
let Test.14 : [<rnnu>C List *self] = TagId(0) Test.7;
let Test.14 : [<rnnu>C List *self] = Reuse #Derived_gen.1 UpdateModeId { id: 1 } TagId(0) Test.7;
ret Test.14;
procedure Test.0 ():
let Test.16 : List [<rnnu>C List *self] = Array [];
let Test.12 : [<rnnu>C List *self] = TagId(0) Test.16;
let Test.10 : [<rnnu>C List *self] = CallByName Test.2 Test.12;
dec Test.12;
dec Test.10;
let Test.11 : Str = "";
ret Test.11;

View file

@ -81,5 +81,6 @@ procedure Test.9 (Test.44, Test.8):
procedure Test.0 ():
let Test.24 : I64 = 4i64;
let Test.17 : [<r>C {}, C I64 {}] = CallByName Test.3 Test.24;
dec Test.17;
let Test.18 : Str = CallByName Test.2;
ret Test.18;

View file

@ -3,9 +3,11 @@ procedure List.2 (List.96, List.97):
let List.513 : Int1 = CallByName Num.22 List.97 List.516;
if List.513 then
let List.515 : I64 = CallByName List.66 List.96 List.97;
dec List.96;
let List.514 : [C {}, C I64] = TagId(1) List.515;
ret List.514;
else
dec List.96;
let List.512 : {} = Struct {};
let List.511 : [C {}, C I64] = TagId(0) List.512;
ret List.511;
@ -44,7 +46,9 @@ procedure Num.22 (#Attr.2, #Attr.3):
ret Num.277;
procedure Test.1 (Test.2, Test.3, Test.4):
inc Test.4;
let Test.29 : [C {}, C I64] = CallByName List.2 Test.4 Test.3;
inc Test.4;
let Test.28 : [C {}, C I64] = CallByName List.2 Test.4 Test.2;
let Test.13 : {[C {}, C I64], [C {}, C I64]} = Struct {Test.28, Test.29};
joinpoint Test.25:

View file

@ -1,4 +1,5 @@
procedure Test.1 (Test.2):
dec Test.2;
let Test.11 : Int1 = false;
ret Test.11;
@ -13,7 +14,6 @@ procedure Test.4 (Test.13):
procedure Test.0 ():
let Test.16 : Str = "abc";
let Test.6 : Int1 = CallByName Test.1 Test.16;
dec Test.16;
let Test.9 : {} = Struct {};
switch Test.6:
case 0:

View file

@ -71,7 +71,6 @@ procedure Json.188 (Json.189, Json.904, #Attr.12):
let Json.914 : {List U8, U64} = Struct {Json.191, Json.926};
let Json.915 : {} = Struct {};
let Json.913 : {List U8, U64} = CallByName List.18 Json.187 Json.914 Json.915;
dec Json.187;
let Json.193 : List U8 = StructAtIndex 0 Json.913;
inc Json.193;
dec Json.913;
@ -159,6 +158,7 @@ procedure List.80 (List.558, List.559, List.560, List.561, List.562):
let List.537 : U64 = CallByName Num.19 List.436 List.538;
jump List.532 List.433 List.535 List.435 List.537 List.437;
else
dec List.433;
ret List.434;
in
jump List.532 List.558 List.559 List.560 List.561 List.562;

View file

@ -19,6 +19,7 @@ procedure Test.15 (Test.49):
ret Test.70;
procedure Test.16 (Test.48):
dec Test.48;
let Test.79 : {} = Struct {};
let Test.78 : Int1 = CallByName Test.13 Test.79;
ret Test.78;
@ -27,11 +28,9 @@ procedure Test.17 (Test.47):
ret Test.47;
procedure Test.35 (Test.36, Test.73):
inc Test.36;
ret Test.36;
procedure Test.37 (Test.38, Test.81):
inc Test.38;
ret Test.38;
procedure Test.40 (Test.41, Test.65, Test.39):
@ -50,7 +49,6 @@ procedure Test.43 (Test.44, Test.42):
joinpoint Test.62 Test.60:
let Test.59 : List U8 = Array [];
let Test.58 : List U8 = CallByName Test.40 Test.59 Test.44 Test.60;
dec Test.59;
ret Test.58;
in
let Test.75 : Int1 = CallByName Bool.2;
@ -59,7 +57,6 @@ procedure Test.43 (Test.44, Test.42):
inc Test.77;
dec Test.42;
let Test.76 : Int1 = CallByName Test.16 Test.77;
dec Test.77;
let Test.61 : Int1 = CallByName Test.14 Test.76;
jump Test.62 Test.61;
else

View file

@ -46,6 +46,11 @@ procedure Encode.23 (Encode.98):
procedure Encode.23 (Encode.98):
ret Encode.98;
procedure Encode.24 (Encode.99, Encode.107, Encode.101):
dec Encode.99;
let Encode.138 : Str = "a Lambda Set is empty. Most likely there is a type error in your program.";
Crash Encode.138
procedure Encode.24 (Encode.99, Encode.107, Encode.101):
let Encode.111 : List U8 = CallByName Test.5 Encode.99 Encode.101 Encode.107;
ret Encode.111;
@ -70,10 +75,6 @@ procedure Encode.24 (Encode.99, Encode.107, Encode.101):
let Encode.134 : List U8 = CallByName Json.188 Encode.99 Encode.101 Encode.107;
ret Encode.134;
procedure Encode.24 (Encode.99, Encode.107, Encode.101):
let Encode.138 : Str = "a Lambda Set is empty. Most likely there is a type error in your program.";
Crash Encode.138
procedure Encode.26 (Encode.105, Encode.106):
let Encode.109 : List U8 = Array [];
let Encode.110 : {{}, {}} = CallByName Test.2 Encode.105;
@ -111,7 +112,6 @@ procedure Json.188 (Json.189, Json.904, #Attr.12):
let Json.914 : {List U8, U64} = Struct {Json.191, Json.926};
let Json.915 : {} = Struct {};
let Json.913 : {List U8, U64} = CallByName List.18 Json.187 Json.914 Json.915;
dec Json.187;
let Json.193 : List U8 = StructAtIndex 0 Json.913;
inc Json.193;
dec Json.913;
@ -150,7 +150,6 @@ procedure Json.188 (Json.189, Json.904, #Attr.12):
let Json.964 : {List U8, U64} = Struct {Json.191, Json.976};
let Json.965 : {} = Struct {};
let Json.963 : {List U8, U64} = CallByName List.18 Json.187 Json.964 Json.965;
dec Json.187;
let Json.193 : List U8 = StructAtIndex 0 Json.963;
inc Json.193;
dec Json.963;
@ -192,7 +191,6 @@ procedure Json.190 (Json.906, Json.196):
dec Json.906;
let Json.975 : {} = Struct {};
let Json.197 : List U8 = CallByName Encode.24 Json.194 Json.196 Json.975;
dec Json.194;
joinpoint Json.970 Json.198:
let Json.968 : U64 = 1i64;
let Json.967 : U64 = CallByName Num.20 Json.195 Json.968;
@ -279,6 +277,7 @@ procedure List.80 (List.551, List.552, List.553, List.554, List.555):
let List.531 : U64 = CallByName Num.19 List.436 List.532;
jump List.526 List.433 List.529 List.435 List.531 List.437;
else
dec List.433;
ret List.434;
in
jump List.526 List.551 List.552 List.553 List.554 List.555;
@ -293,6 +292,7 @@ procedure List.80 (List.624, List.625, List.626, List.627, List.628):
let List.604 : U64 = CallByName Num.19 List.436 List.605;
jump List.599 List.433 List.602 List.435 List.604 List.437;
else
dec List.433;
ret List.434;
in
jump List.599 List.624 List.625 List.626 List.627 List.628;

View file

@ -68,10 +68,12 @@ procedure List.80 (List.544, List.545, List.546, List.547, List.548):
let List.523 : U64 = CallByName Num.19 List.436 List.524;
jump List.518 List.433 List.438 List.435 List.523 List.437;
else
dec List.433;
let List.439 : U64 = UnionAtIndex (Id 0) (Index 0) List.521;
let List.525 : [C U64, C U64] = TagId(0) List.439;
ret List.525;
else
dec List.433;
let List.519 : [C U64, C U64] = TagId(1) List.434;
ret List.519;
in
@ -102,6 +104,7 @@ procedure Test.3 (Test.4, Test.12):
procedure Test.0 (Test.1):
let Test.10 : U64 = 0i64;
let Test.11 : {} = Struct {};
inc Test.1;
let Test.2 : U64 = CallByName List.26 Test.1 Test.10 Test.11;
let Test.9 : U64 = 0i64;
let Test.7 : Int1 = CallByName Bool.11 Test.2 Test.9;

View file

@ -20,7 +20,7 @@ use roc_reporting::report::{can_problem, type_problem, RocDocAllocator};
use roc_solve_problem::TypeError;
use roc_types::{
pretty_print::{name_and_print_var, DebugPrint},
subs::{Subs, Variable},
subs::{instantiate_rigids, Subs, Variable},
};
fn promote_expr_to_module(src: &str) -> String {
@ -44,8 +44,9 @@ fn promote_expr_to_module(src: &str) -> String {
buffer
}
pub fn run_load_and_infer(
pub fn run_load_and_infer<'a>(
src: &str,
dependencies: impl IntoIterator<Item = (&'a str, &'a str)>,
no_promote: bool,
) -> Result<(LoadedModule, String), std::io::Error> {
use tempfile::tempdir;
@ -65,6 +66,11 @@ pub fn run_load_and_infer(
let loaded = {
let dir = tempdir()?;
for (file, source) in dependencies {
std::fs::write(dir.path().join(format!("{file}.roc")), source)?;
}
let filename = PathBuf::from("Test.roc");
let file_path = dir.path().join(filename);
let result = roc_load::load_and_typecheck_str(
@ -248,9 +254,9 @@ fn parse_queries(src: &str, line_info: &LineInfo) -> Vec<TypeQuery> {
#[derive(Default, Clone, Copy)]
pub struct InferOptions {
pub allow_errors: bool,
pub print_can_decls: bool,
pub print_only_under_alias: bool,
pub allow_errors: bool,
pub no_promote: bool,
}
@ -338,7 +344,12 @@ impl InferredProgram {
}
}
pub fn infer_queries(src: &str, options: InferOptions) -> Result<InferredProgram, Box<dyn Error>> {
pub fn infer_queries<'a>(
src: &str,
dependencies: impl IntoIterator<Item = (&'a str, &'a str)>,
options: InferOptions,
allow_can_errors: bool,
) -> Result<InferredProgram, Box<dyn Error>> {
let (
LoadedModule {
module_id: home,
@ -351,7 +362,7 @@ pub fn infer_queries(src: &str, options: InferOptions) -> Result<InferredProgram
..
},
src,
) = run_load_and_infer(src, options.no_promote)?;
) = run_load_and_infer(src, dependencies, options.no_promote)?;
let declarations = declarations_by_id.remove(&home).unwrap();
let subs = solved.inner_mut();
@ -359,23 +370,20 @@ pub fn infer_queries(src: &str, options: InferOptions) -> Result<InferredProgram
let can_problems = can_problems.remove(&home).unwrap_or_default();
let type_problems = type_problems.remove(&home).unwrap_or_default();
if !options.allow_errors {
{
let (can_problems, type_problems) =
format_problems(&src, home, &interns, can_problems, type_problems);
if !can_problems.is_empty() {
if !can_problems.is_empty() && !allow_can_errors {
return Err(format!("Canonicalization problems: {can_problems}",).into());
}
if !type_problems.is_empty() {
if !type_problems.is_empty() && !options.allow_errors {
return Err(format!("Type problems: {type_problems}",).into());
}
}
let line_info = LineInfo::new(&src);
let queries = parse_queries(&src, &line_info);
if queries.is_empty() {
return Err("No queries provided!".into());
}
let mut inferred_queries = Vec::with_capacity(queries.len());
let exposed_by_module = ExposedByModule::default();
@ -533,6 +541,7 @@ impl<'a> QueryCtx<'a> {
let def_region = Region::new(start_pos, end_pos);
let def_source = &self.source[start_pos.offset as usize..end_pos.offset as usize];
instantiate_rigids(self.subs, def.var());
roc_late_solve::unify(
self.home,
self.arena,
@ -560,40 +569,3 @@ impl<'a> QueryCtx<'a> {
})
}
}
pub fn infer_queries_help(src: &str, expected: impl FnOnce(&str), options: InferOptions) {
let InferredProgram {
program,
inferred_queries,
} = infer_queries(src, options).unwrap();
let mut output_parts = Vec::with_capacity(inferred_queries.len() + 2);
if options.print_can_decls {
use roc_can::debug::{pretty_print_declarations, PPCtx};
let ctx = PPCtx {
home: program.home,
interns: &program.interns,
print_lambda_names: true,
};
let pretty_decls = pretty_print_declarations(&ctx, &program.declarations);
output_parts.push(pretty_decls);
output_parts.push("\n".to_owned());
}
for InferredQuery { elaboration, .. } in inferred_queries {
let output_part = match elaboration {
Elaboration::Specialization {
specialized_name,
typ,
} => format!("{specialized_name} : {typ}"),
Elaboration::Source { source, typ } => format!("{source} : {typ}"),
Elaboration::Instantiation { .. } => panic!("Use uitest instead"),
};
output_parts.push(output_part);
}
let pretty_output = output_parts.join("\n");
expected(&pretty_output);
}

View file

@ -171,23 +171,15 @@ impl RecordField<Type> {
}
}
pub fn instantiate_aliases<'a, F>(
fn instantiate_aliases<'a, F>(
&mut self,
region: Region,
aliases: &'a F,
var_store: &mut VarStore,
new_lambda_sets: &mut ImSet<Variable>,
new_infer_ext_vars: &mut ImSet<Variable>,
aliases: &F,
ctx: &mut InstantiateAliasesCtx<'_>,
) where
F: Fn(Symbol) -> Option<&'a Alias>,
{
self.as_inner_mut().instantiate_aliases(
region,
aliases,
var_store,
new_lambda_sets,
new_infer_ext_vars,
)
instantiate_aliases(self.as_inner_mut(), region, aliases, ctx)
}
pub fn contains_symbol(&self, rep_symbol: Symbol) -> bool {
@ -229,20 +221,12 @@ impl LambdaSet {
fn instantiate_aliases<'a, F>(
&mut self,
region: Region,
aliases: &'a F,
var_store: &mut VarStore,
new_lambda_sets: &mut ImSet<Variable>,
new_infer_ext_vars: &mut ImSet<Variable>,
aliases: &F,
ctx: &mut InstantiateAliasesCtx<'_>,
) where
F: Fn(Symbol) -> Option<&'a Alias>,
{
self.0.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_sets,
new_infer_ext_vars,
)
instantiate_aliases(&mut self.0, region, aliases, ctx)
}
}
@ -1335,6 +1319,16 @@ impl Types {
cloned
}
pub fn shallow_dealias(&self, value: Index<TypeTag>) -> Index<TypeTag> {
let mut result = value;
while let TypeTag::StructuralAlias { actual, .. } | TypeTag::OpaqueAlias { actual, .. } =
self[result]
{
result = actual;
}
result
}
}
#[cfg(debug_assertions)]
@ -1699,6 +1693,8 @@ impl_types_index! {
impl_types_index_slice! {
tag_names, TagName
field_names, Lowercase
tags, TypeTag
}
impl std::ops::Index<Index<AsideTypeSlice>> for Types {
@ -2317,7 +2313,16 @@ impl Type {
stack.push(ext);
}
}
RecursiveTagUnion(_, tags, ext) => {
RecursiveTagUnion(rec, tags, ext) => {
if let Some(replacement) = substitutions.get(rec) {
let new_rec_var = match replacement {
Type::Variable(v) => *v,
_ => panic!("Recursion var substitution must be a variable"),
};
*rec = new_rec_var;
}
for (_, args) in tags {
stack.extend(args.iter_mut());
}
@ -2806,7 +2811,7 @@ impl Type {
}
/// a shallow dealias, continue until the first constructor is not an alias.
pub fn shallow_dealias(&self) -> &Self {
fn shallow_dealias(&self) -> &Self {
let mut result = self;
while let Type::Alias { actual, .. } = result {
result = actual;
@ -2830,370 +2835,21 @@ impl Type {
pub fn instantiate_aliases<'a, F>(
&mut self,
region: Region,
aliases: &'a F,
aliases: &F,
var_store: &mut VarStore,
new_lambda_set_variables: &mut ImSet<Variable>,
new_recursion_variables: &mut ImSet<Variable>,
new_infer_ext_vars: &mut ImSet<Variable>,
) where
F: Fn(Symbol) -> Option<&'a Alias>,
{
use Type::*;
match self {
Function(args, closure, ret) => {
for arg in args {
arg.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
closure.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
ret.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
FunctionOrTagUnion(_, _, ext) => {
if let TypeExtension::Open(ext, _) = ext {
ext.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
}
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
for (_, args) in tags {
for x in args {
x.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
}
if let TypeExtension::Open(ext, _) = ext {
ext.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
}
Record(fields, ext) => {
for (_, x) in fields.iter_mut() {
x.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
if let TypeExtension::Open(ext, _) = ext {
ext.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
}
Tuple(elems, ext) => {
for (_, x) in elems.iter_mut() {
x.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
if let TypeExtension::Open(ext, _) = ext {
ext.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
}
DelayedAlias(AliasCommon {
type_arguments,
lambda_set_variables,
infer_ext_in_output_types,
symbol: _,
}) => {
debug_assert!(lambda_set_variables
.iter()
.all(|lambda_set| matches!(lambda_set.0, Type::Variable(..))));
debug_assert!(infer_ext_in_output_types
.iter()
.all(|t| matches!(t, Type::Variable(..) | Type::EmptyTagUnion)));
type_arguments.iter_mut().for_each(|t| {
t.value.typ.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
)
});
}
HostExposedAlias {
type_arguments: type_args,
lambda_set_variables,
actual: actual_type,
..
} => {
for arg in type_args {
arg.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
for arg in lambda_set_variables {
arg.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
actual_type.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
Alias {
type_arguments: type_args,
lambda_set_variables,
actual: actual_type,
..
} => {
for arg in type_args {
arg.typ.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
for arg in lambda_set_variables {
arg.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
actual_type.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
Apply(symbol, args, _) => {
if let Some(alias) = aliases(*symbol) {
// TODO switch to this, but we still need to check for recursion with the
// `else` branch.
// We would also need to determine polarity correct.
if false {
let mut type_var_to_arg = Vec::new();
for (alias_var, arg_ann) in alias.type_variables.iter().zip(args) {
type_var_to_arg.push(Loc::at(
arg_ann.region,
OptAbleType {
typ: arg_ann.value.clone(),
opt_abilities: alias_var.value.opt_bound_abilities.clone(),
},
));
}
let mut lambda_set_variables =
Vec::with_capacity(alias.lambda_set_variables.len());
for _ in 0..alias.lambda_set_variables.len() {
let lvar = var_store.fresh();
new_lambda_set_variables.insert(lvar);
lambda_set_variables.push(LambdaSet(Type::Variable(lvar)));
}
let mut infer_ext_in_output_types =
Vec::with_capacity(alias.infer_ext_in_output_variables.len());
for _ in 0..alias.infer_ext_in_output_variables.len() {
let var = var_store.fresh();
new_infer_ext_vars.insert(var);
infer_ext_in_output_types.push(Type::Variable(var));
}
let alias = Type::DelayedAlias(AliasCommon {
symbol: *symbol,
type_arguments: type_var_to_arg,
lambda_set_variables,
infer_ext_in_output_types,
});
*self = alias;
} else {
if args.len() != alias.type_variables.len() {
// We will have already reported an error during canonicalization.
*self = Type::Error;
return;
}
let mut actual = alias.typ.clone();
let mut named_args = Vec::with_capacity(args.len());
let mut substitution = ImMap::default();
// TODO substitute further in args
for (
Loc {
value:
AliasVar {
var: placeholder,
opt_bound_abilities,
..
},
..
},
filler,
) in alias.type_variables.iter().zip(args.iter())
{
let mut filler = filler.clone();
filler.value.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
named_args.push(OptAbleType {
typ: filler.value.clone(),
opt_abilities: opt_bound_abilities.clone(),
});
substitution.insert(*placeholder, filler.value);
}
// make sure hidden variables are freshly instantiated
let mut lambda_set_variables =
Vec::with_capacity(alias.lambda_set_variables.len());
for typ in alias.lambda_set_variables.iter() {
if let Type::Variable(var) = typ.0 {
let fresh = var_store.fresh();
new_lambda_set_variables.insert(fresh);
substitution.insert(var, Type::Variable(fresh));
lambda_set_variables.push(LambdaSet(Type::Variable(fresh)));
} else {
unreachable!("at this point there should be only vars in there");
}
}
let mut infer_ext_in_output_types =
Vec::with_capacity(alias.infer_ext_in_output_variables.len());
for var in alias.infer_ext_in_output_variables.iter() {
let fresh = var_store.fresh();
new_infer_ext_vars.insert(fresh);
substitution.insert(*var, Type::Variable(fresh));
infer_ext_in_output_types.push(Type::Variable(fresh));
}
actual.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
actual.substitute(&substitution);
// instantiate recursion variable!
if let Type::RecursiveTagUnion(rec_var, mut tags, mut ext) = actual {
let new_rec_var = var_store.fresh();
substitution.clear();
substitution.insert(rec_var, Type::Variable(new_rec_var));
for typ in tags.iter_mut().flat_map(|v| v.1.iter_mut()) {
typ.substitute(&substitution);
}
if let TypeExtension::Open(ext, _) = &mut ext {
ext.substitute(&substitution);
}
actual = Type::RecursiveTagUnion(new_rec_var, tags, ext);
}
let alias = Type::Alias {
symbol: *symbol,
type_arguments: named_args,
lambda_set_variables,
infer_ext_in_output_types,
actual: Box::new(actual),
kind: alias.kind,
};
*self = alias;
}
} else {
// one of the special-cased Apply types.
for x in args {
x.value.instantiate_aliases(
region,
aliases,
var_store,
new_lambda_set_variables,
new_infer_ext_vars,
);
}
}
}
RangedNumber(_) => {}
UnspecializedLambdaSet { .. } => {}
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Error | Variable(_) => {}
}
let mut ctx = InstantiateAliasesCtx {
var_store,
new_lambda_set_variables,
new_recursion_variables,
new_infer_ext_vars,
};
instantiate_aliases(self, region, aliases, &mut ctx)
}
pub fn instantiate_lambda_sets_as_unspecialized(
@ -3269,6 +2925,208 @@ impl Type {
}
}
struct InstantiateAliasesCtx<'a> {
var_store: &'a mut VarStore,
new_lambda_set_variables: &'a mut ImSet<Variable>,
new_recursion_variables: &'a mut ImSet<Variable>,
new_infer_ext_vars: &'a mut ImSet<Variable>,
}
fn instantiate_aliases<'a, F>(
typ: &mut Type,
region: Region,
aliases: &F,
ctx: &mut InstantiateAliasesCtx<'_>,
) where
F: Fn(Symbol) -> Option<&'a Alias>,
{
use Type::*;
match typ {
Function(args, closure, ret) => {
for arg in args {
instantiate_aliases(arg, region, aliases, ctx);
}
instantiate_aliases(closure, region, aliases, ctx);
instantiate_aliases(ret, region, aliases, ctx);
}
FunctionOrTagUnion(_, _, ext) => {
if let TypeExtension::Open(ext, _) = ext {
instantiate_aliases(ext, region, aliases, ctx);
}
}
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
for (_, args) in tags {
for x in args {
instantiate_aliases(x, region, aliases, ctx);
}
}
if let TypeExtension::Open(ext, _) = ext {
instantiate_aliases(ext, region, aliases, ctx);
}
}
Record(fields, ext) => {
for (_, x) in fields.iter_mut() {
x.instantiate_aliases(region, aliases, ctx);
}
if let TypeExtension::Open(ext, _) = ext {
instantiate_aliases(ext, region, aliases, ctx);
}
}
Tuple(elems, ext) => {
for (_, x) in elems.iter_mut() {
instantiate_aliases(x, region, aliases, ctx);
}
if let TypeExtension::Open(ext, _) = ext {
instantiate_aliases(ext, region, aliases, ctx);
}
}
DelayedAlias(AliasCommon {
type_arguments,
lambda_set_variables,
infer_ext_in_output_types,
symbol: _,
}) => {
debug_assert!(lambda_set_variables
.iter()
.all(|lambda_set| matches!(lambda_set.0, Type::Variable(..))));
debug_assert!(infer_ext_in_output_types
.iter()
.all(|t| matches!(t, Type::Variable(..) | Type::EmptyTagUnion)));
type_arguments
.iter_mut()
.for_each(|t| instantiate_aliases(&mut t.value.typ, region, aliases, ctx));
}
HostExposedAlias {
type_arguments: type_args,
lambda_set_variables,
actual: actual_type,
..
} => {
for arg in type_args {
instantiate_aliases(arg, region, aliases, ctx);
}
for arg in lambda_set_variables {
arg.instantiate_aliases(region, aliases, ctx);
}
instantiate_aliases(&mut *actual_type, region, aliases, ctx);
}
Alias {
type_arguments: type_args,
lambda_set_variables,
actual: actual_type,
..
} => {
for arg in type_args {
instantiate_aliases(&mut arg.typ, region, aliases, ctx);
}
for arg in lambda_set_variables {
arg.instantiate_aliases(region, aliases, ctx);
}
instantiate_aliases(actual_type, region, aliases, ctx);
}
Apply(symbol, args, _) => {
if let Some(alias) = aliases(*symbol) {
if args.len() != alias.type_variables.len() {
// We will have already reported an error during canonicalization.
*typ = Type::Error;
return;
}
let mut actual = alias.typ.clone();
let mut named_args = Vec::with_capacity(args.len());
let mut substitution = ImMap::default();
// TODO substitute further in args
for (
Loc {
value:
AliasVar {
var: placeholder,
opt_bound_abilities,
..
},
..
},
filler,
) in alias.type_variables.iter().zip(args.iter())
{
let mut filler = filler.clone();
instantiate_aliases(&mut filler.value, region, aliases, ctx);
named_args.push(OptAbleType {
typ: filler.value.clone(),
opt_abilities: opt_bound_abilities.clone(),
});
substitution.insert(*placeholder, filler.value);
}
// make sure nested lambda set variables are freshly instantiated
let mut lambda_set_variables = Vec::with_capacity(alias.lambda_set_variables.len());
for typ in alias.lambda_set_variables.iter() {
if let Type::Variable(var) = typ.0 {
let fresh = ctx.var_store.fresh();
ctx.new_lambda_set_variables.insert(fresh);
substitution.insert(var, Type::Variable(fresh));
lambda_set_variables.push(LambdaSet(Type::Variable(fresh)));
} else {
unreachable!("at this point there should be only vars in there");
}
}
// make sure all nested recursion variables are freshly instantiated
let mut recursion_variables = Vec::with_capacity(alias.recursion_variables.len());
for var in alias.recursion_variables.iter() {
let fresh = ctx.var_store.fresh();
ctx.new_recursion_variables.insert(fresh);
substitution.insert(*var, Type::Variable(fresh));
recursion_variables.push(Type::Variable(fresh));
}
// make sure all nested infer-open-in-output position ext vars are freshly instantiated
let mut infer_ext_in_output_types =
Vec::with_capacity(alias.infer_ext_in_output_variables.len());
for var in alias.infer_ext_in_output_variables.iter() {
let fresh = ctx.var_store.fresh();
ctx.new_infer_ext_vars.insert(fresh);
substitution.insert(*var, Type::Variable(fresh));
infer_ext_in_output_types.push(Type::Variable(fresh));
}
instantiate_aliases(&mut actual, region, aliases, ctx);
actual.substitute(&substitution);
let alias = Type::Alias {
symbol: *symbol,
type_arguments: named_args,
lambda_set_variables,
infer_ext_in_output_types,
actual: Box::new(actual),
kind: alias.kind,
};
*typ = alias;
} else {
// one of the special-cased Apply types.
for x in args {
instantiate_aliases(&mut x.value, region, aliases, ctx);
}
}
}
RangedNumber(_) => {}
UnspecializedLambdaSet { .. } => {}
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Error | Variable(_) => {}
}
}
fn symbols_help(initial: &Type) -> Vec<Symbol> {
use Type::*;

View file

@ -14,8 +14,12 @@ harness = false
[dev-dependencies]
roc_builtins = { path = "../builtins" }
roc_collections = { path = "../collections" }
roc_derive = { path = "../derive", features = ["debug-derived-symbols"] }
roc_load = { path = "../load" }
roc_packaging = { path = "../../packaging" }
roc_module = { path = "../module", features = ["debug-symbols"] }
roc_mono = { path = "../mono" }
roc_parse = { path = "../parse" }
roc_problem = { path = "../problem" }
roc_reporting = { path = "../../reporting" }

View file

@ -0,0 +1,156 @@
use std::io;
use bumpalo::Bump;
use roc_collections::MutMap;
use roc_load::{ExecutionMode, LoadConfig, LoadMonomorphizedError, Threading};
use roc_module::symbol::{Interns, Symbol};
use roc_mono::{
ir::{Proc, ProcLayout},
layout::STLayoutInterner,
};
use tempfile::tempdir;
use test_solve_helpers::format_problems;
#[derive(Default)]
pub struct MonoOptions {
pub no_check: bool,
}
pub fn write_compiled_ir<'a>(
writer: &mut impl io::Write,
test_module: &str,
dependencies: impl IntoIterator<Item = (&'a str, &'a str)>,
options: MonoOptions,
allow_can_errors: bool,
) -> io::Result<()> {
use roc_packaging::cache::RocCacheDir;
use std::path::PathBuf;
let exec_mode = ExecutionMode::Executable;
let arena = &Bump::new();
let dir = tempdir()?;
for (file, source) in dependencies {
std::fs::write(dir.path().join(format!("{file}.roc")), source)?;
}
let filename = PathBuf::from("Test.roc");
let file_path = dir.path().join(filename);
let load_config = LoadConfig {
target_info: roc_target::TargetInfo::default_x86_64(),
threading: Threading::Single,
render: roc_reporting::report::RenderTarget::Generic,
palette: roc_reporting::report::DEFAULT_PALETTE,
exec_mode,
};
let loaded = roc_load::load_and_monomorphize_from_str(
arena,
file_path,
test_module,
dir.path().to_path_buf(),
RocCacheDir::Disallowed,
load_config,
);
let loaded = match loaded {
Ok(x) => x,
Err(LoadMonomorphizedError::LoadingProblem(roc_load::LoadingProblem::FormattedReport(
report,
))) => {
println!("{}", report);
panic!();
}
Err(e) => panic!("{:?}", e),
};
use roc_load::MonomorphizedModule;
let MonomorphizedModule {
procedures,
exposed_to_host,
mut layout_interner,
interns,
can_problems,
mut type_problems,
sources,
..
} = loaded;
let main_fn_symbol = exposed_to_host.top_level_values.keys().copied().next();
for (module, can_problems) in can_problems.into_iter() {
let type_problems = type_problems.remove(&module).unwrap_or_default();
let source = sources.get(&module).unwrap();
let (can_problems, type_problems) =
format_problems(&source.1, module, &interns, can_problems, type_problems);
if !can_problems.is_empty() && !allow_can_errors {
panic!("Canonicalization problems: {can_problems}");
}
if !type_problems.is_empty() {
panic!("Type problems: {type_problems}");
}
}
if !options.no_check {
check_procedures(arena, &interns, &mut layout_interner, &procedures);
}
write_procedures(writer, layout_interner, procedures, main_fn_symbol)
}
fn check_procedures<'a>(
arena: &'a Bump,
interns: &Interns,
interner: &mut STLayoutInterner<'a>,
procedures: &MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
) {
use roc_mono::debug::{check_procs, format_problems};
let problems = check_procs(arena, interner, procedures);
if problems.is_empty() {
return;
}
let formatted = format_problems(interns, interner, problems);
panic!("IR problems found:\n{formatted}");
}
fn write_procedures<'a>(
writer: &mut impl io::Write,
interner: STLayoutInterner<'a>,
procedures: MutMap<(Symbol, ProcLayout<'a>), Proc<'a>>,
opt_main_fn_symbol: Option<Symbol>,
) -> io::Result<()> {
let mut procs_strings = procedures
.values()
.map(|proc| proc.to_pretty(&interner, 200, false))
.collect::<Vec<_>>();
let opt_main_fn = opt_main_fn_symbol.map(|main_fn_symbol| {
let index = procedures
.keys()
.position(|(s, _)| *s == main_fn_symbol)
.unwrap();
procs_strings.swap_remove(index)
});
procs_strings.sort();
if let Some(main_fn) = opt_main_fn {
procs_strings.push(main_fn);
}
let mut procs = procs_strings.iter().peekable();
while let Some(proc) = procs.next() {
if procs.peek().is_some() {
writeln!(writer, "{}", proc)?;
} else {
write!(writer, "{}", proc)?;
}
}
Ok(())
}

View file

@ -8,11 +8,15 @@ use std::{
use lazy_static::lazy_static;
use libtest_mimic::{run, Arguments, Failed, Trial};
use mono::MonoOptions;
use regex::Regex;
use roc_collections::VecMap;
use test_solve_helpers::{
infer_queries, Elaboration, InferOptions, InferredProgram, InferredQuery, MUTLILINE_MARKER,
};
mod mono;
fn main() -> Result<(), Box<dyn Error>> {
let args = Arguments::from_args();
@ -32,13 +36,25 @@ lazy_static! {
.join("uitest")
.join("tests");
/// # +opt can:<opt>
static ref RE_OPT_CAN: Regex =
Regex::new(r#"# \+opt can:(?P<opt>.*)"#).unwrap();
/// # +opt infer:<opt>
static ref RE_OPT_INFER: Regex =
Regex::new(r#"# \+opt infer:(?P<opt>.*)"#).unwrap();
/// # +opt print:<opt>
static ref RE_OPT_PRINT: Regex =
Regex::new(r#"# \+opt print:(?P<opt>.*)"#).unwrap();
/// # +opt mono:<opt>
static ref RE_OPT_MONO: Regex =
Regex::new(r#"# \+opt mono:(?P<opt>.*)"#).unwrap();
/// # +emit:<opt>
static ref RE_EMIT: Regex =
Regex::new(r#"# \+emit:(?P<opt>.*)"#).unwrap();
/// ## module <name>
static ref RE_MODULE: Regex =
Regex::new(r#"## module (?P<name>.*)"#).unwrap();
}
fn collect_uitest_files() -> io::Result<Vec<PathBuf>> {
@ -78,12 +94,22 @@ fn into_test(path: PathBuf) -> io::Result<Trial> {
fn run_test(path: PathBuf) -> Result<(), Failed> {
let data = std::fs::read_to_string(&path)?;
let TestCase {
can_options,
infer_options,
print_options,
source,
} = TestCase::parse(data)?;
emit_options,
mono_options,
program,
} = TestCase::parse(&data)?;
let inferred_program = infer_queries(&source, infer_options)?;
let inferred_program = infer_queries(
program.test_module,
program
.other_modules
.iter()
.map(|(md, src)| (&**md, &**src)),
infer_options,
can_options.allow_errors,
)?;
{
let mut fd = fs::OpenOptions::new()
@ -91,7 +117,14 @@ fn run_test(path: PathBuf) -> Result<(), Failed> {
.truncate(true)
.open(&path)?;
assemble_query_output(&mut fd, &source, inferred_program, print_options)?;
assemble_query_output(
&mut fd,
program,
inferred_program,
can_options,
mono_options,
emit_options,
)?;
}
check_for_changes(&path)?;
@ -101,32 +134,116 @@ fn run_test(path: PathBuf) -> Result<(), Failed> {
const EMIT_HEADER: &str = "# -emit:";
struct TestCase {
struct Modules<'a> {
before_any: &'a str,
other_modules: VecMap<&'a str, &'a str>,
test_module: &'a str,
}
struct TestCase<'a> {
can_options: CanOptions,
infer_options: InferOptions,
print_options: PrintOptions,
source: String,
mono_options: MonoOptions,
emit_options: EmitOptions,
program: Modules<'a>,
}
#[derive(Default)]
struct PrintOptions {
can_decls: bool,
struct CanOptions {
allow_errors: bool,
}
impl TestCase {
fn parse(mut data: String) -> Result<Self, Failed> {
#[derive(Default)]
struct EmitOptions {
can_decls: bool,
mono: bool,
}
impl<'a> TestCase<'a> {
fn parse(mut data: &'a str) -> Result<Self, Failed> {
// Drop anything following `# -emit:` header lines; that's the output.
if let Some(drop_at) = data.find(EMIT_HEADER) {
data.truncate(drop_at);
data.truncate(data.trim_end().len());
data = data[..drop_at].trim_end();
}
let can_options = Self::parse_can_options(data)?;
let infer_options = Self::parse_infer_options(data)?;
let mono_options = Self::parse_mono_options(data)?;
let emit_options = Self::parse_emit_options(data)?;
let program = Self::parse_modules(data);
Ok(TestCase {
infer_options: Self::parse_infer_options(&data)?,
print_options: Self::parse_print_options(&data)?,
source: data,
can_options,
infer_options,
mono_options,
emit_options,
program,
})
}
fn parse_modules(data: &'a str) -> Modules<'a> {
let mut module_starts = RE_MODULE.captures_iter(data).peekable();
let first_module_start = match module_starts.peek() {
None => {
// This is just a single module with no name; it is the test module.
return Modules {
before_any: Default::default(),
other_modules: Default::default(),
test_module: data,
};
}
Some(p) => p.get(0).unwrap().start(),
};
let before_any = data[..first_module_start].trim();
let mut test_module = None;
let mut other_modules = VecMap::new();
while let Some(module_start) = module_starts.next() {
let module_name = module_start.name("name").unwrap().as_str();
let module_start = module_start.get(0).unwrap().end();
let module = match module_starts.peek() {
None => &data[module_start..],
Some(next_module_start) => {
let module_end = next_module_start.get(0).unwrap().start();
&data[module_start..module_end]
}
}
.trim();
if module_name == "Test" {
test_module = Some(module);
} else {
other_modules.insert(module_name, module);
}
}
let test_module = test_module.expect("no Test module found");
Modules {
before_any,
other_modules,
test_module,
}
}
fn parse_can_options(data: &str) -> Result<CanOptions, Failed> {
let mut can_opts = CanOptions::default();
let found_can_opts = RE_OPT_CAN.captures_iter(data);
for can_opt in found_can_opts {
let opt = can_opt.name("opt").unwrap().as_str();
match opt.trim() {
"allow_errors" => can_opts.allow_errors = true,
other => return Err(format!("unknown can option: {other:?}").into()),
}
}
Ok(can_opts)
}
fn parse_infer_options(data: &str) -> Result<InferOptions, Failed> {
let mut infer_opts = InferOptions {
no_promote: true,
@ -146,19 +263,35 @@ impl TestCase {
Ok(infer_opts)
}
fn parse_print_options(data: &str) -> Result<PrintOptions, Failed> {
let mut print_opts = PrintOptions::default();
fn parse_mono_options(data: &str) -> Result<MonoOptions, Failed> {
let mut mono_opts = MonoOptions::default();
let found_infer_opts = RE_OPT_PRINT.captures_iter(data);
let found_infer_opts = RE_OPT_MONO.captures_iter(data);
for infer_opt in found_infer_opts {
let opt = infer_opt.name("opt").unwrap().as_str();
match opt.trim() {
"can_decls" => print_opts.can_decls = true,
other => return Err(format!("unknown print option: {other:?}").into()),
"no_check" => mono_opts.no_check = true,
other => return Err(format!("unknown mono option: {other:?}").into()),
}
}
Ok(print_opts)
Ok(mono_opts)
}
fn parse_emit_options(data: &str) -> Result<EmitOptions, Failed> {
let mut emit_opts = EmitOptions::default();
let found_infer_opts = RE_EMIT.captures_iter(data);
for infer_opt in found_infer_opts {
let opt = infer_opt.name("opt").unwrap().as_str();
match opt.trim() {
"can_decls" => emit_opts.can_decls = true,
"mono" => emit_opts.mono = true,
other => return Err(format!("unknown emit option: {other:?}").into()),
}
}
Ok(emit_opts)
}
}
@ -184,25 +317,60 @@ fn check_for_changes(path: &Path) -> Result<(), Failed> {
/// Assemble the output for a test, with queries elaborated in-line.
fn assemble_query_output(
writer: &mut impl io::Write,
source: &str,
program: Modules<'_>,
inferred_program: InferredProgram,
print_options: PrintOptions,
can_options: CanOptions,
mono_options: MonoOptions,
emit_options: EmitOptions,
) -> io::Result<()> {
let Modules {
before_any,
other_modules,
test_module,
} = program;
if !before_any.is_empty() {
writeln!(writer, "{before_any}\n")?;
}
for (module, source) in other_modules.iter() {
writeln!(writer, "## module {module}")?;
writeln!(writer, "{}\n", source)?;
}
if !other_modules.is_empty() {
writeln!(writer, "## module Test")?;
}
// Reverse the queries so that we can pop them off the end as we pass through the lines.
let (queries, program) = inferred_program.decompose();
let mut sorted_queries = queries.into_sorted();
sorted_queries.reverse();
let mut reflow = Reflow::new_unindented(writer);
write_source_with_answers(&mut reflow, source, sorted_queries, 0)?;
write_source_with_answers(&mut reflow, test_module, sorted_queries, 0)?;
// Finish up with any remaining print options we were asked to provide.
let PrintOptions { can_decls } = print_options;
// Finish up with any remaining emit options we were asked to provide.
let EmitOptions { can_decls, mono } = emit_options;
if can_decls {
writeln!(writer, "\n{EMIT_HEADER}can_decls")?;
program.write_can_decls(writer)?;
}
if mono {
writeln!(writer, "\n{EMIT_HEADER}mono")?;
// Unfortunately, with the current setup we must now recompile into the IR.
// TODO: extend the data returned by a monomorphized module to include
// that of a solved module.
mono::write_compiled_ir(
writer,
test_module,
other_modules,
mono_options,
can_options.allow_errors,
)?;
}
Ok(())
}

View file

@ -1,5 +1,5 @@
# +opt print:can_decls
# +opt infer:print_only_under_alias
# +emit:can_decls
app "test" provides [main] to "./platform"
Parser a : {} -> a

View file

@ -0,0 +1,17 @@
app "test" provides [main] to "./platform"
main =
foo : a, Bool -> Str
foo = \in, b -> if b then "done" else bar in
# ^^^ a -[[bar(2)]]-> Str
# ^^^ a, Bool -[[foo(1)]]-> Str
bar = \_ -> foo {} Bool.true
# ^^^ {}, Bool -[[]]-> Str
foo "" Bool.false
# ^^^{inst} Str, Bool -[[foo(1)]]-> Str
# │ foo : a, Bool -> Str
# │ foo = \in, b -> if b then "done" else bar in
# │ ^^^ Str -[[bar(2)]]-> Str
# │ ^^^ Str, Bool -[[foo(1)]]-> Str

View file

@ -0,0 +1,17 @@
app "test" provides [main] to "./platform"
main =
f = \x -> x + 1
map : { f1: (I64 -> I64) } -> List I64
map = \{ f1 } -> List.concat [f1 1] (map { f1 })
# ^^^ { f1 : I64 -[[]]-> I64 } -[[map(2)]]-> List I64
# ^^^ { f1 : I64 -[[]]-> I64 } -[[map(2)]]-> List I64
map { f1: f }
# ^^^{inst} { f1 : I64 -[[f(1)]]-> I64 } -[[map(2)]]-> List I64
# │ map : { f1: (I64 -> I64) } -> List I64
# │ map = \{ f1 } -> List.concat [f1 1] (map { f1 })
# │ ^^^ { f1 : I64 -[[f(1)]]-> I64 } -[[map(2)]]-> List I64
# │ ^^^ { f1 : I64 -[[f(1)]]-> I64 } -[[map(2)]]-> List I64

View file

@ -0,0 +1,18 @@
# +opt can:allow_errors
# +opt infer:print_only_under_alias
app "test" provides [single] to "./platform"
LL a : [Nil, Cons a (LL a)]
LinkedList a : LL a
single : a -> LinkedList a
single = \item -> (Cons item Nil)
#^^^^^^{-1} a -[[single(0)]]-> [Cons a b, Nil]* as b
walk : LinkedList elem, state, (state, elem -> state) -> state
walk = \list, state, fn ->
#^^^^{-1} [Cons elem a, Nil] as a, state, (state, elem -[[]]-> state) -[[walk(3)]]-> state
when list is
Nil -> state
Cons first rest -> walk (rest) (fn state first) fn

View file

@ -0,0 +1,16 @@
# +opt can:allow_errors
# +opt infer:print_only_under_alias
app "test" provides [single] to "./platform"
LL a : [Nil, Cons a (LL a)]
LinkedList a := LL a
single = \item -> @LinkedList (Cons item Nil)
#^^^^^^{-1} a -[[single(0)]]-> [Cons a b, Nil] as b
walk = \@LinkedList list, state, fn ->
#^^^^{-1} [Cons a b, Nil] as b, c, (c, a -[[]]-> c) -[[walk(3)]]-> c
when list is
Nil -> state
Cons first rest -> walk (@LinkedList rest) (fn state first) fn

View file

@ -0,0 +1,25 @@
# +opt infer:print_only_under_alias
app "test" provides [main] to "./platform"
Effect : {} -> Str
after = \fx, toNext ->
afterInner = \{} ->
fxOut = fx {}
next = toNext fxOut
next {}
afterInner
await : Effect, (Str -> Effect) -> Effect
await = \fx, cont -> after fx (\result -> cont result)
line : Str -> Effect
line = \s -> \{} -> s
main =
#^^^^{-1} {} -[[afterInner(8) ({} -[[afterInner(8) ({} -a-> Str) (Str -[[13 (Str -[[20 Str]]-> ({} -[[16 Str]]-> Str))]]-> ({} -[[16 Str]]-> Str)), 16 Str] as a]-> Str) (Str -[[13 (Str -[[21]]-> ({} -[[16 Str]]-> Str))]]-> ({} -[[16 Str]]-> Str))] as [[afterInner(8) ({} -[[afterInner(8) ({} -a-> Str) (Str -[[13 (Str -[[20 Str]]-> ({} -[[16 Str]]-> Str))]]-> ({} -[[16 Str]]-> Str)), 16 Str] as a]-> Str) (Str -[[13 (Str -[[21]]-> ({} -[[16 Str]]-> Str))]]-> ({} -[[16 Str]]-> Str))] as b]]-> Str
await
(List.walk ["a", "b"] (line "printing letters") (\state, elem -> await state (\_ -> line elem)))
(\_ -> line "")

View file

@ -0,0 +1,32 @@
# +emit:mono
# +opt mono:no_check
## module Dep
interface Dep exposes [defaultRequest] imports []
defaultRequest = {
url: "",
body: "",
}
## module Test
app "test" imports [Dep.{ defaultRequest }] provides [main] to "./platform"
main =
{ defaultRequest & url: "http://www.example.com" }
# -emit:mono
procedure Dep.0 ():
let Dep.2 : Str = "";
let Dep.3 : Str = "";
let Dep.1 : {Str, Str} = Struct {Dep.2, Dep.3};
ret Dep.1;
procedure Test.0 ():
let Test.3 : Str = "http://www.example.com";
let Test.4 : {Str, Str} = CallByName Dep.0;
let Test.2 : Str = StructAtIndex 0 Test.4;
inc Test.2;
dec Test.4;
let Test.1 : {Str, Str} = Struct {Test.2, Test.3};
ret Test.1;

View file

@ -626,19 +626,42 @@ samp .comment,
code .comment {
color: var(--green);
}
/* Number, String, Tag, Type literals */
/* Number, String, Tag literals */
samp .storage.type,
code .storage.type,
samp .string,
code .string,
samp .string.begin,
code .string.begin,
samp .string.end,
code .string.end,
samp .constant,
code .constant,
samp .literal,
code .literal {
color: var(--cyan);
}
/* Keywords and punctuation */
samp .kw,
samp .keyword,
code .keyword,
samp .punctuation.section,
code .punctuation.section,
samp .punctuation.separator,
code .punctuation.separator,
samp .punctuation.terminator,
code .punctuation.terminator,
samp .kw,
code .kw {
color: var(--magenta);
color: var(--magenta);
}
/* Operators */
samp .op,
code .op {
code .op,
samp .keyword.operator,
code .keyword.operator {
color: var(--orange);
}
@ -649,12 +672,22 @@ code .delimeter {
}
/* Variables modules and field names */
samp .function,
code .function,
samp .meta.group,
code .meta.group,
samp .meta.block,
code .meta.block,
samp .lowerident,
code .lowerident {
color: var(--blue);
}
/* Types, Tags, and Modules */
samp .type,
code .type,
samp .meta.path,
code .meta.path,
samp .upperident,
code .upperident {
color: var(--green);

View file

@ -360,27 +360,30 @@ fn jit_to_ast_help<'a, A: ReplApp<'a>>(
})
}
Layout::Builtin(Builtin::Int(int_width)) => {
use Content::*;
use IntWidth::*;
match (env.subs.get_content_without_compacting(raw_var), int_width) {
(Alias(Symbol::NUM_UNSIGNED8 | Symbol::NUM_U8, ..), U8) => num_helper!(u8),
(_, U8) => {
// This is not a number, it's a tag union or something else
app.call_function(main_fn_name, |_mem: &A::Memory, num: u8| {
byte_to_ast(env, num, env.subs.get_content_without_compacting(raw_var))
})
match int_width {
U8 => {
let raw_content = env.subs.get_content_without_compacting(raw_var);
if matches!(raw_content, Content::Alias(name, ..) if name.module_id() == ModuleId::NUM)
{
num_helper!(u8)
} else {
// This is not a number, it's a tag union or something else
app.call_function(main_fn_name, |_mem: &A::Memory, num: u8| {
byte_to_ast(env, num, env.subs.get_content_without_compacting(raw_var))
})
}
}
// The rest are numbers... for now
(_, U16) => num_helper!(u16),
(_, U32) => num_helper!(u32),
(_, U64) => num_helper!(u64),
(_, U128) => num_helper!(u128),
(_, I8) => num_helper!(i8),
(_, I16) => num_helper!(i16),
(_, I32) => num_helper!(i32),
(_, I64) => num_helper!(i64),
(_, I128) => num_helper!(i128),
U16 => num_helper!(u16),
U32 => num_helper!(u32),
U64 => num_helper!(u64),
U128 => num_helper!(u128),
I8 => num_helper!(i8),
I16 => num_helper!(i16),
I32 => num_helper!(i32),
I64 => num_helper!(i64),
I128 => num_helper!(i128),
}
}
Layout::Builtin(Builtin::Float(float_width)) => {

View file

@ -1180,4 +1180,74 @@ mod test {
),
);
}
#[test]
fn tuple_access() {
run_expect_test(
indoc!(
r#"
interface Test exposes [] imports []
expect
t = ("One", "Two")
t.1 == "One"
"#
),
indoc!(
r#"
This expectation failed:
3> expect
4> t = ("One", "Two")
5> t.1 == "One"
When it failed, these variables had these values:
t : (
Str,
Str,
)a
t = ("One", "Two")
"#
),
);
}
#[test]
fn match_on_opaque_number_type() {
run_expect_test(
indoc!(
r#"
interface Test exposes [] imports []
hexToByte : U8, U8 -> U8
hexToByte = \upper, lower ->
Num.bitwiseOr (Num.shiftRightBy upper 4) lower
expect
actual = hexToByte 7 4
expected = 't'
actual == expected
"#
),
indoc!(
r#"
This expectation failed:
7> expect
8> actual = hexToByte 7 4
9> expected = 't'
10> actual == expected
When it failed, these variables had these values:
actual : U8
actual = 4
expected : Int Unsigned8
expected = 116
"#
),
);
}
}

View file

@ -1138,8 +1138,8 @@ fn to_expr_report<'b>(
),
}
}
Reason::FnCall { name, arity } => match count_arguments(&found) {
0 => {
Reason::FnCall { name, arity } => match describe_wanted_function(&found) {
DescribedFunction::NotAFunction(tag) => {
let this_value = match name {
None => alloc.text("This value"),
Some(symbol) => alloc.concat([
@ -1149,30 +1149,43 @@ fn to_expr_report<'b>(
]),
};
let lines = vec![
alloc.concat([
this_value,
alloc.string(format!(
" is not a function, but it was given {}:",
if arity == 1 {
"1 argument".into()
} else {
format!("{} arguments", arity)
}
)),
use NotAFunctionTag::*;
let doc = match tag {
OpaqueNeedsUnwrap => alloc.stack([
alloc.concat([
this_value,
alloc.reflow(
" is an opaque type, so it cannot be called with an argument:",
),
]),
alloc.region(lines.convert_region(expr_region)),
alloc.reflow("I can't call an opaque type because I don't know what it is! Maybe you meant to unwrap it first?"),
]),
alloc.region(lines.convert_region(expr_region)),
alloc.reflow("Are there any missing commas? Or missing parentheses?"),
];
Other => alloc.stack([
alloc.concat([
this_value,
alloc.string(format!(
" is not a function, but it was given {}:",
if arity == 1 {
"1 argument".into()
} else {
format!("{} arguments", arity)
}
)),
]),
alloc.region(lines.convert_region(expr_region)),
alloc.reflow("Are there any missing commas? Or missing parentheses?"),
]),
};
Report {
filename,
title: "TOO MANY ARGS".to_string(),
doc: alloc.stack(lines),
doc,
severity,
}
}
n => {
DescribedFunction::Arguments(n) => {
let this_function = match name {
None => alloc.text("This function"),
Some(symbol) => alloc.concat([
@ -1543,13 +1556,35 @@ fn does_not_implement<'a>(
])
}
fn count_arguments(tipe: &ErrorType) -> usize {
enum DescribedFunction {
Arguments(usize),
NotAFunction(NotAFunctionTag),
}
enum NotAFunctionTag {
OpaqueNeedsUnwrap,
Other,
}
fn describe_wanted_function(tipe: &ErrorType) -> DescribedFunction {
use ErrorType::*;
match tipe {
Function(args, _, _) => args.len(),
Alias(_, _, actual, _) => count_arguments(actual),
_ => 0,
Function(args, _, _) => DescribedFunction::Arguments(args.len()),
Alias(_, _, actual, AliasKind::Structural) => describe_wanted_function(actual),
Alias(_, _, actual, AliasKind::Opaque) => {
let tag = if matches!(
describe_wanted_function(actual),
DescribedFunction::Arguments(_)
) {
NotAFunctionTag::OpaqueNeedsUnwrap
} else {
NotAFunctionTag::Other
};
DescribedFunction::NotAFunction(tag)
}
_ => DescribedFunction::NotAFunction(NotAFunctionTag::Other),
}
}

View file

@ -1237,20 +1237,20 @@ mod test_reporting {
@r###"
TYPE MISMATCH /code/proj/Main.roc
This 1st argument to `f` has an unexpected type:
This expression is used in an unexpected way:
7 g = \x -> f [x]
^^^
^^^^^
The argument is a list of type:
This `f` call produces:
List List b
But you are trying to use it as:
List b
But `f` needs its 1st argument to be:
a
Tip: The type annotation uses the type variable `a` to say that this
Tip: The type annotation uses the type variable `b` to say that this
definition can produce any type of value. But in the body I see that
it will only produce a `List` value of a single specific type. Maybe
change the type annotation to be more specific? Maybe change the code
@ -12689,42 +12689,6 @@ I recommend using camelCase. It's the standard style in Roc code!
)
);
test_report!(
polymorphic_recursion_forces_ungeneralized_type,
indoc!(
r#"
foo : a, Bool -> Str
foo = \in, b -> if b then "done" else bar in
bar = \_ -> foo {} Bool.true
foo "" Bool.false
"#
),
@r###"
TYPE MISMATCH /code/proj/Main.roc
This 1st argument to `foo` has an unexpected type:
9 foo "" Bool.false
^^
The argument is a string of type:
Str
But `foo` needs its 1st argument to be:
a
Tip: The type annotation uses the type variable `a` to say that this
definition can produce any type of value. But in the body I see that
it will only produce a `Str` value of a single specific type. Maybe
change the type annotation to be more specific? Maybe change the code
to be more general?
"###
);
test_report!(
suggest_binding_rigid_var_to_ability,
indoc!(
@ -13417,4 +13381,33 @@ I recommend using camelCase. It's the standard style in Roc code!
"#
)
);
test_report!(
apply_opaque_as_function,
indoc!(
r#"
app "test" provides [main] to "./platform"
Parser a := Str -> a
parser : Parser Str
parser = @Parser \s -> Str.concat s "asd"
main : Str
main = parser "hi"
"#
),
@r###"
TOO MANY ARGS /code/proj/Main.roc
The `parser` value is an opaque type, so it cannot be called with an
argument:
9 main = parser "hi"
^^^^^^
I can't call an opaque type because I don't know what it is! Maybe you
meant to unwrap it first?
"###
);
}

View file

@ -8,6 +8,7 @@ version.workspace = true
[dev-dependencies]
cli_utils = { path = "../cli_utils" }
roc_command_utils = { path = "../utils/command" }
roc_build = { path = "../compiler/build" }
roc_linker = { path = "../linker" }
roc_load = { path = "../compiler/load" }
@ -20,5 +21,14 @@ indoc.workspace = true
target-lexicon.workspace = true
tempfile.workspace = true
[features]
default = ["target-aarch64", "target-x86_64", "target-wasm32"]
target-aarch64 = ["roc_build/target-aarch64"]
target-arm = []
target-wasm32 = []
target-x86 = []
target-x86_64 = ["roc_build/target-x86_64"]
[package.metadata.cargo-udeps.ignore]
development = ["roc_build", "roc_linker"]
development = ["roc_build", "roc_linker"]

View file

@ -10,9 +10,8 @@ fn build_host() {
use roc_build::program::build_and_preprocess_host;
use roc_linker::preprocessed_host_filename;
let platform_main_roc = std::env::current_dir()
.unwrap()
.join("zig-platform/main.roc");
let platform_main_roc =
roc_command_utils::root_dir().join("crates/valgrind/zig-platform/main.roc");
// tests always run on the host
let target = target_lexicon::Triple::host();
@ -58,15 +57,16 @@ fn valgrind_test_linux(source: &str) {
// the host is identical for all tests so we only want to build it once
BUILD_ONCE.call_once(build_host);
let pf = std::env::current_dir()
.unwrap()
.join("zig-platform/main.roc");
let pf = roc_command_utils::root_dir().join("crates/valgrind/zig-platform/main.roc");
assert!(pf.exists(), "cannot find platform {:?}", &pf);
let mut app_module_source = format!(
indoc::indoc!(
r#"
let concat_header = !source.trim().starts_with("app ");
let mut app_module_source = if concat_header {
format!(
indoc::indoc!(
r#"
app "test"
packages {{ pf: "{}" }}
imports []
@ -74,16 +74,26 @@ fn valgrind_test_linux(source: &str) {
main =
"#
),
pf.to_str().unwrap()
);
),
pf.to_str().unwrap()
)
} else {
String::new()
};
for line in source.lines() {
app_module_source.push_str(" ");
if concat_header {
app_module_source.push_str(" ");
}
app_module_source.push_str(line);
app_module_source.push('\n');
}
if !concat_header {
app_module_source =
app_module_source.replace("replace_me_platform_path", &pf.display().to_string());
}
let temp_dir = tempfile::tempdir().unwrap();
let app_module_path = temp_dir.path().join("app.roc");
@ -319,3 +329,224 @@ fn str_concat_later_referencing_empty_list_with_capacity() {
"#
));
}
#[test]
fn joinpoint_with_closure() {
valgrind_test(indoc!(
r#"
(
Animal : [Cat, Dog, Goose]
makeSound : Animal -> Str
makeSound = \animal ->
dogSound = "Woof"
when animal is
Cat | Dog if isCat animal -> "Miauw"
Goose -> "Honk"
_ -> dogSound
isCat : Animal -> Bool
isCat = \animal ->
when animal is
Cat -> Bool.true
_ -> Bool.false
test =
catSound = makeSound Cat
dogSound = makeSound Dog
gooseSound = makeSound Goose
"Cat: \(catSound), Dog: \(dogSound), Goose: \(gooseSound)"
test
)
"#
));
}
#[test]
fn joinpoint_with_reuse() {
valgrind_test(indoc!(
r#"
(
LinkedList a : [Cons a (LinkedList a), Nil]
# mapLinkedList : LinkedList a, (a -> b) -> LinkedList b
mapLinkedList = \linkedList, f -> when linkedList is
Nil -> Nil
Cons x xs ->
x2 = if Bool.true then x else x
Cons (f x2) (mapLinkedList xs f)
# printLinkedList : LinkedList a, (a -> Str) -> Str
printLinkedList = \linkedList, f ->
when linkedList is
Nil -> "Nil"
Cons x xs ->
strX = f x
strXs = printLinkedList xs f
"Cons \(strX) (\(strXs))"
test =
newList = mapLinkedList (Cons 1 (Cons 2 (Cons 3 Nil))) (\x -> x + 1)
printLinkedList newList Num.toStr
test
)
"#
));
}
#[test]
fn tree_rebalance() {
valgrind_test(indoc!(
r#"
app "test"
packages { pf: "replace_me_platform_path" }
imports []
provides [main] to pf
main = show (insert 0 {} Empty)
insert : Key k, v, RedBlackTree (Key k) v -> RedBlackTree (Key k) v
insert = \key, value, dict ->
when insertHelp key value dict is
Node Red k v l r -> Node Black k v l r
x -> x
insertHelp : Key k, v, RedBlackTree (Key k) v -> RedBlackTree (Key k) v
insertHelp = \key, value, dict ->
when dict is
Empty ->
# New nodes are always red. If it violates the rules, it will be fixed
# when balancing.
Node Red key value Empty Empty
Node nColor nKey nValue nLeft nRight ->
when Num.compare key nKey is
LT -> balance nColor nKey nValue (insertHelp key value nLeft) nRight
EQ -> Node nColor nKey value nLeft nRight
GT -> balance nColor nKey nValue nLeft (insertHelp key value nRight)
balance : NodeColor, k, v, RedBlackTree k v, RedBlackTree k v -> RedBlackTree k v
balance = \color, key, value, left, right ->
when right is
Node Red rK rV rLeft rRight ->
when left is
Node Red lK lV lLeft lRight ->
Node
Red
key
value
(Node Black lK lV lLeft lRight)
(Node Black rK rV rLeft rRight)
_ ->
Node color rK rV (Node Red key value left rLeft) rRight
_ ->
when left is
Node Red lK lV (Node Red llK llV llLeft llRight) lRight ->
Node
Red
lK
lV
(Node Black llK llV llLeft llRight)
(Node Black key value lRight right)
_ ->
Node color key value left right
show : RedBlackTree I64 {} -> Str
show = \tree -> showRBTree tree Num.toStr (\{} -> "{}")
showRBTree : RedBlackTree k v, (k -> Str), (v -> Str) -> Str
showRBTree = \tree, showKey, showValue ->
when tree is
Empty -> "Empty"
Node color key value left right ->
sColor = showColor color
sKey = showKey key
sValue = showValue value
sL = nodeInParens left showKey showValue
sR = nodeInParens right showKey showValue
"Node \(sColor) \(sKey) \(sValue) \(sL) \(sR)"
nodeInParens : RedBlackTree k v, (k -> Str), (v -> Str) -> Str
nodeInParens = \tree, showKey, showValue ->
when tree is
Empty ->
showRBTree tree showKey showValue
Node _ _ _ _ _ ->
inner = showRBTree tree showKey showValue
"(\(inner))"
showColor : NodeColor -> Str
showColor = \color ->
when color is
Red -> "Red"
Black -> "Black"
NodeColor : [Red, Black]
RedBlackTree k v : [Node NodeColor k v (RedBlackTree k v) (RedBlackTree k v), Empty]
Key k : Num k
"#
));
}
#[test]
fn lowlevel_list_calls() {
valgrind_test(indoc!(
r#"
(
a = List.map [1,1,1,1,1] (\x -> x + 0)
b = List.map2 a [1,1,1,1,1] (\x, y -> x + y)
c = List.map3 a b [1,1,1,1,1] (\x, y, z -> x + y + z)
d = List.map4 a b c [1,1,1,1,1] (\x, y, z, w -> x + y + z + w)
e = List.sortWith d (\x, y -> Num.compare x y)
Num.toStr (List.len e)
)
"#
));
}
#[test]
fn joinpoint_nullpointer() {
valgrind_test(indoc!(
r#"
(
LinkedList a : [Cons a (LinkedList a), Nil]
printLinkedList : LinkedList Str -> Str
printLinkedList = \linkedList->
when linkedList is
Nil -> "Nil"
Cons x xs ->
strXs = printLinkedList xs
"Cons \(x) (\(strXs))"
linkedListHead : LinkedList Str -> LinkedList Str
linkedListHead = \linkedList ->
string = when linkedList is
Cons s _ -> s
Nil -> ""
Cons string Nil
test =
cons = printLinkedList (linkedListHead (Cons "foo" Nil))
nil = printLinkedList (linkedListHead (Nil))
"\(cons) - \(nil)"
test
)
"#
));
}

View file

@ -54,7 +54,6 @@ impl<'a> WasiDispatcher<'a> {
memory: &mut [u8],
) -> Option<Value> {
let success_code = Some(Value::I32(Errno::Success as i32));
match function_name {
"args_get" => {
// uint8_t ** argv,
@ -97,7 +96,37 @@ impl<'a> WasiDispatcher<'a> {
"fd_allocate" => todo!("WASI {}({:?})", function_name, arguments),
"fd_close" => todo!("WASI {}({:?})", function_name, arguments),
"fd_datasync" => todo!("WASI {}({:?})", function_name, arguments),
"fd_fdstat_get" => todo!("WASI {}({:?})", function_name, arguments),
"fd_fdstat_get" => {
// (i32, i32) -> i32
// file descriptor
let fd = arguments[0].expect_i32().unwrap() as usize;
// ptr to a wasi_fdstat_t
let stat_mut_ptr = arguments[1].expect_i32().unwrap() as usize;
match fd {
1 => {
// Tell WASI that stdout is a tty (no seek or tell)
// https://github.com/WebAssembly/wasi-libc/blob/659ff414560721b1660a19685110e484a081c3d4/libc-bottom-half/sources/isatty.c
// *Not* a tty if:
// (statbuf.fs_filetype != __WASI_FILETYPE_CHARACTER_DEVICE ||
// (statbuf.fs_rights_base & (__WASI_RIGHTS_FD_SEEK | __WASI_RIGHTS_FD_TELL)) != 0)
// So it's sufficient to set:
// .fs_filetype = __WASI_FILETYPE_CHARACTER_DEVICE
// .fs_rights_base = 0
const WASI_FILETYPE_CHARACTER_DEVICE: u8 = 2;
memory[stat_mut_ptr] = WASI_FILETYPE_CHARACTER_DEVICE;
for b in memory[stat_mut_ptr + 1..stat_mut_ptr + 24].iter_mut() {
*b = 0;
}
}
_ => todo!("WASI {}({:?})", function_name, arguments),
}
success_code
}
"fd_fdstat_set_flags" => todo!("WASI {}({:?})", function_name, arguments),
"fd_fdstat_set_rights" => todo!("WASI {}({:?})", function_name, arguments),
"fd_filestat_get" => todo!("WASI {}({:?})", function_name, arguments),
@ -211,14 +240,16 @@ impl<'a> WasiDispatcher<'a> {
let mut n_written: i32 = 0;
let mut negative_length_count = 0;
for _ in 0..iovs_len {
let mut write_result = Ok(());
for i in 0..iovs_len {
// https://man7.org/linux/man-pages/man2/readv.2.html
// struct iovec {
// void *iov_base; /* Starting address */
// size_t iov_len; /* Number of bytes to transfer */
// };
let iov_base = read_u32(memory, ptr_iovs) as usize;
let iov_len = read_i32(memory, ptr_iovs + 4);
let ptr_iov = ptr_iovs + (8 * i as usize); // index into the array of iovec's
let iov_base = read_u32(memory, ptr_iov) as usize;
let iov_len = read_i32(memory, ptr_iov + 4);
if iov_len < 0 {
// I found negative-length iov's when I implemented this in JS for the web REPL (see wasi.js)
// I'm not sure why, but this solution worked, and it's the same WASI libc - there's only one.
@ -228,18 +259,15 @@ impl<'a> WasiDispatcher<'a> {
}
let bytes = &memory[iov_base..][..iov_len as usize];
match &mut write_lock {
WriteLock::StdOut(stdout) => {
n_written += stdout.write(bytes).unwrap() as i32;
}
WriteLock::Stderr(stderr) => {
n_written += stderr.write(bytes).unwrap() as i32;
}
WriteLock::RegularFile(content) => {
content.extend_from_slice(bytes);
n_written += bytes.len() as i32;
}
write_result = match &mut write_lock {
WriteLock::StdOut(stdout) => stdout.write_all(bytes),
WriteLock::Stderr(stderr) => stderr.write_all(bytes),
WriteLock::RegularFile(content) => content.write_all(bytes),
};
if write_result.is_err() {
break;
}
n_written += bytes.len() as i32;
}
write_i32(memory, ptr_nwritten, n_written);
@ -251,7 +279,10 @@ impl<'a> WasiDispatcher<'a> {
);
}
success_code
match write_result {
Ok(()) => success_code,
Err(_) => Some(Value::I32(Errno::Io as i32)),
}
}
"path_create_directory" => todo!("WASI {}({:?})", function_name, arguments),
"path_filestat_get" => todo!("WASI {}({:?})", function_name, arguments),

View file

@ -18,6 +18,7 @@ path = "src/main.rs"
[dependencies]
libc = "0.2"
syntect = "5.0"
roc_highlight = { path = "../../../crates/highlight" }
roc_std = { path = "../../../crates/roc_std" }

View file

@ -8,6 +8,12 @@ use std::fs;
use std::os::raw::c_char;
use std::path::{Path, PathBuf};
use syntect::easy::HighlightLines;
use syntect::parsing::SyntaxSet;
use syntect::highlighting::{ThemeSet, Style};
use syntect::util::{LinesWithEndings};
use syntect::html::{ClassedHTMLGenerator, ClassStyle};
extern "C" {
#[link_name = "roc__transformFileContentForHost_1_exposed"]
fn roc_transformFileContentForHost(relPath: &RocStr, content: &RocStr) -> RocStr;
@ -210,21 +216,34 @@ fn process_file(input_dir: &Path, output_dir: &Path, input_file: &Path) -> Resul
// And track a little bit of state
let mut in_code_block = false;
let mut is_roc_code = false;
let syntax_set : syntect::parsing::SyntaxSet = SyntaxSet::load_defaults_newlines();
let theme_set : syntect::highlighting::ThemeSet = ThemeSet::load_defaults();
for event in parser {
match event {
pulldown_cmark::Event::Code(cow_str) => {
let highlighted_html =
roc_highlight::highlight_roc_code_inline(cow_str.to_string().as_str());
parser_with_highlighting.push(pulldown_cmark::Event::Html(
pulldown_cmark::CowStr::from(highlighted_html),
));
pulldown_cmark::Event::Code(code_str) => {
if code_str.starts_with("roc!") {
let stripped = code_str
.strip_prefix("roc!")
.expect("expected leading 'roc!'");
let highlighted_html = roc_highlight::highlight_roc_code_inline(stripped.to_string().as_str());
parser_with_highlighting.push(pulldown_cmark::Event::Html(
pulldown_cmark::CowStr::from(highlighted_html),
));
} else {
let inline_code = pulldown_cmark::CowStr::from(format!("<code>{}</code>", code_str));
parser_with_highlighting.push(
pulldown_cmark::Event::Html(inline_code)
);
}
}
pulldown_cmark::Event::Start(pulldown_cmark::Tag::CodeBlock(cbk)) => {
in_code_block = true;
is_roc_code = is_roc_code_block(&cbk);
}
pulldown_cmark::Event::End(pulldown_cmark::Tag::CodeBlock(_)) => {
pulldown_cmark::Event::End(pulldown_cmark::Tag::CodeBlock(pulldown_cmark::CodeBlockKind::Fenced(extention_str))) => {
if in_code_block {
match replace_code_with_static_file(&code_to_highlight, input_file) {
None => {}
@ -243,6 +262,14 @@ fn process_file(input_dir: &Path, output_dir: &Path, input_file: &Path) -> Resul
let highlighted_html: String;
if is_roc_code {
highlighted_html = roc_highlight::highlight_roc_code(&code_to_highlight)
} else if let Some(syntax) = syntax_set.find_syntax_by_token(&extention_str) {
let mut h = HighlightLines::new(syntax, &theme_set.themes["base16-ocean.dark"]);
let mut html_generator = ClassedHTMLGenerator::new_with_class_style(syntax, &syntax_set, ClassStyle::Spaced);
for line in LinesWithEndings::from(&code_to_highlight) {
html_generator.parse_html_for_line_which_includes_newline(line);
}
highlighted_html = format!("<pre><samp>{}</pre></samp>", html_generator.finalize())
} else {
highlighted_html = format!("<pre><samp>{}</pre></samp>", &code_to_highlight)
}
@ -361,4 +388,4 @@ fn replace_code_with_static_file(code: &str, input_file: &Path) -> Option<String
}
}
}
}
}

View file

@ -635,48 +635,78 @@ h4 {
/* Comments `#` and Documentation comments `##` */
samp .comment,
code .comment {
color: var(--green);
color: var(--green);
}
/* Number, String, Tag, Type literals */
/* Number, String, Tag literals */
samp .storage.type,
code .storage.type,
samp .string,
code .string,
samp .string.begin,
code .string.begin,
samp .string.end,
code .string.end,
samp .constant,
code .constant,
samp .literal,
code .literal {
color: var(--cyan);
color: var(--cyan);
}
/* Keywords and punctuation */
samp .kw,
samp .keyword,
code .keyword,
samp .punctuation.section,
code .punctuation.section,
samp .punctuation.separator,
code .punctuation.separator,
samp .punctuation.terminator,
code .punctuation.terminator,
samp .kw,
code .kw {
color: var(--magenta);
}
/* Operators */
samp .op,
code .op {
color: var(--orange);
code .op,
samp .keyword.operator,
code .keyword.operator {
color: var(--orange);
}
/* Delimieters */
samp .delimeter,
code .delimeter {
color: var(--gray);
color: var(--gray);
}
/* Variables modules and field names */
samp .function,
code .function,
samp .meta.group,
code .meta.group,
samp .meta.block,
code .meta.block,
samp .lowerident,
code .lowerident {
color: var(--blue);
color: var(--blue);
}
/* Types, Tags, and Modules */
samp .type,
code .type,
samp .meta.path,
code .meta.path,
samp .upperident,
code .upperident {
color: var(--green);
color: var(--green);
}
samp .dim,
code .dim {
opacity: 0.55;
opacity: 0.55;
}
.button-container {