mirror of
https://github.com/roc-lang/roc.git
synced 2024-11-11 05:34:11 +03:00
Merge pull request #4209 from roc-lang/impl-tag-discriminant
Derive `Hash` implementations for tag unions
This commit is contained in:
commit
83b64c4fb3
@ -3,16 +3,23 @@
|
||||
use std::iter::once;
|
||||
|
||||
use roc_can::{
|
||||
expr::{AnnotatedMark, ClosureData, Expr, Recursive},
|
||||
expr::{AnnotatedMark, ClosureData, Expr, IntValue, Recursive, WhenBranch, WhenBranchPattern},
|
||||
num::{IntBound, IntLitWidth},
|
||||
pattern::Pattern,
|
||||
};
|
||||
use roc_derive_key::hash::FlatHashKey;
|
||||
use roc_module::{called_via::CalledVia, ident::Lowercase, symbol::Symbol};
|
||||
use roc_region::all::Loc;
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::{
|
||||
called_via::CalledVia,
|
||||
ident::{Lowercase, TagName},
|
||||
symbol::Symbol,
|
||||
};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::{
|
||||
num::int_lit_width_to_variable,
|
||||
subs::{
|
||||
Content, FlatType, LambdaSet, OptVariable, RecordFields, SubsSlice, UnionLambdas, Variable,
|
||||
VariableSubsSlice,
|
||||
Content, ExhaustiveMark, FlatType, GetSubsSlice, LambdaSet, OptVariable, RecordFields,
|
||||
RedundantMark, SubsIndex, SubsSlice, UnionLambdas, UnionTags, Variable, VariableSubsSlice,
|
||||
},
|
||||
types::RecordField,
|
||||
};
|
||||
@ -20,8 +27,15 @@ use roc_types::{
|
||||
use crate::{synth_var, util::Env, DerivedBody};
|
||||
|
||||
pub(crate) fn derive_hash(env: &mut Env<'_>, key: FlatHashKey, def_symbol: Symbol) -> DerivedBody {
|
||||
let (body, body_type) = match key {
|
||||
let (body_type, body) = match key {
|
||||
FlatHashKey::Record(fields) => hash_record(env, def_symbol, fields),
|
||||
FlatHashKey::TagUnion(tags) => {
|
||||
if tags.len() == 1 {
|
||||
hash_newtype_tag_union(env, def_symbol, tags.into_iter().next().unwrap())
|
||||
} else {
|
||||
hash_tag_union(env, def_symbol, tags)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let specialization_lambda_sets =
|
||||
@ -34,7 +48,7 @@ pub(crate) fn derive_hash(env: &mut Env<'_>, key: FlatHashKey, def_symbol: Symbo
|
||||
}
|
||||
}
|
||||
|
||||
fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (Expr, Variable) {
|
||||
fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (Variable, Expr) {
|
||||
// Suppose rcd = { f1, ..., fn }.
|
||||
// Build a generalized type t_rcd = { f1: t1, ..., fn: tn }, with fresh t1, ..., tn,
|
||||
// so that we can re-use the derived impl for many records of the same fields.
|
||||
@ -75,9 +89,9 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (E
|
||||
let hasher_sym = env.new_symbol("hasher");
|
||||
let hasher_var = synth_var(env.subs, Content::FlexAbleVar(None, Symbol::HASH_HASHER));
|
||||
|
||||
let (body, body_var) = record_fields.iter_all().fold(
|
||||
(Expr::Var(hasher_sym), hasher_var),
|
||||
|(body, body_var), (field_name, field_var, _)| {
|
||||
let (body_var, body) = record_fields.iter_all().fold(
|
||||
(hasher_var, Expr::Var(hasher_sym)),
|
||||
|total_hasher, (field_name, field_var, _)| {
|
||||
let field_name = env.subs[field_name].clone();
|
||||
let field_var = env.subs[field_var];
|
||||
|
||||
@ -89,54 +103,326 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (E
|
||||
field: field_name,
|
||||
};
|
||||
|
||||
let (hash_fn_data, returned_hasher_var) = {
|
||||
// build `Hash.hash ...` function type
|
||||
//
|
||||
// hasher, val -[uls]-> hasher | hasher has Hasher, val has Hash
|
||||
let exposed_hash_fn_var = env.import_builtin_symbol_var(Symbol::HASH_HASH);
|
||||
|
||||
// (typeof body), (typeof field) -[clos]-> hasher_result
|
||||
let this_arguments_slice =
|
||||
VariableSubsSlice::insert_into_subs(env.subs, [body_var, field_var]);
|
||||
let this_hash_clos_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_hasher_result_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_hash_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
this_arguments_slice,
|
||||
this_hash_clos_var,
|
||||
this_hasher_result_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// hasher, val -[uls]-> hasher | hasher has Hasher, val has Hash
|
||||
// ~ (typeof body), (typeof field) -[clos]-> hasher_result
|
||||
env.unify(exposed_hash_fn_var, this_hash_fn_var);
|
||||
|
||||
// Hash.hash : hasher, (typeof field) -[clos]-> hasher | hasher has Hasher, (typeof field) has Hash
|
||||
let hash_fn_head = Expr::AbilityMember(Symbol::HASH_HASH, None, this_hash_fn_var);
|
||||
let hash_fn_data = Box::new((
|
||||
this_hash_fn_var,
|
||||
Loc::at_zero(hash_fn_head),
|
||||
this_hash_clos_var,
|
||||
this_hasher_result_var,
|
||||
));
|
||||
|
||||
(hash_fn_data, this_hasher_result_var)
|
||||
};
|
||||
|
||||
let hash_arguments = vec![
|
||||
(body_var, Loc::at_zero(body)),
|
||||
(field_var, Loc::at_zero(field_access)),
|
||||
];
|
||||
let call_hash = Expr::Call(hash_fn_data, hash_arguments, CalledVia::Space);
|
||||
|
||||
(call_hash, returned_hasher_var)
|
||||
call_hash_hash(env, total_hasher, (field_var, field_access))
|
||||
},
|
||||
);
|
||||
|
||||
// Finally, build the closure
|
||||
// \hasher, rcd -> body
|
||||
build_outer_derived_closure(
|
||||
env,
|
||||
fn_name,
|
||||
(hasher_var, hasher_sym),
|
||||
(record_var, Pattern::Identifier(rcd_sym)),
|
||||
(body_var, body),
|
||||
)
|
||||
}
|
||||
|
||||
/// Build a `hash` implementation for a non-singleton tag union.
|
||||
fn hash_tag_union(
|
||||
env: &mut Env<'_>,
|
||||
fn_name: Symbol,
|
||||
tags: Vec<(TagName, u16)>,
|
||||
) -> (Variable, Expr) {
|
||||
// Suppose tags = [ A p11 .. p1n, ..., Q pq1 .. pqm ]
|
||||
// Build a generalized type t_tags = [ A t11 .. t1n, ..., Q tq1 .. tqm ],
|
||||
// with fresh t1, ..., tqm, so that we can re-use the derived impl for many
|
||||
// unions of the same tags and payloads.
|
||||
let (union_var, union_tags) = {
|
||||
let flex_tag_labels = tags
|
||||
.into_iter()
|
||||
.map(|(label, arity)| {
|
||||
let variables_slice = VariableSubsSlice::reserve_into_subs(env.subs, arity.into());
|
||||
for var_index in variables_slice {
|
||||
env.subs[var_index] = env.subs.fresh_unnamed_flex_var();
|
||||
}
|
||||
(label, variables_slice)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let union_tags = UnionTags::insert_slices_into_subs(env.subs, flex_tag_labels);
|
||||
let tag_union_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::TagUnion(union_tags, Variable::EMPTY_TAG_UNION)),
|
||||
);
|
||||
|
||||
(tag_union_var, union_tags)
|
||||
};
|
||||
|
||||
// Now, a hasher for this tag union is
|
||||
//
|
||||
// hash_union : hasher, [ A t11 .. t1n, ..., Q tq1 .. tqm ] -> hasher | hasher has Hasher
|
||||
// hash_union = \hasher, union ->
|
||||
// when union is
|
||||
// A x11 .. x1n -> Hash.hash (... (Hash.hash (Hash.uN hasher 0) x11) ...) x1n
|
||||
// ...
|
||||
// Q xq1 .. xqm -> Hash.hash (... (Hash.hash (Hash.uN hasher (q - 1)) xq1) ...) xqm
|
||||
//
|
||||
// where `Hash.uN` is the appropriate hasher for the discriminant value - typically a `u8`, but
|
||||
// if there are more than `u8::MAX` tags, we use `u16`, and so on.
|
||||
let union_sym = env.new_symbol("union");
|
||||
|
||||
let hasher_sym = env.new_symbol("hasher");
|
||||
let hasher_var = synth_var(env.subs, Content::FlexAbleVar(None, Symbol::HASH_HASHER));
|
||||
|
||||
let (discr_width, discr_precision_var, hash_discr_member) = if union_tags.len() > u64::MAX as _
|
||||
{
|
||||
// Should never happen, `usize` isn't more than 64 bits on most machines, but who knows?
|
||||
// Maybe someday soon someone will try to compile a huge Roc program on a 128-bit one.
|
||||
internal_error!("new record unlocked: you fit more than 18 billion, billion tags in a Roc program, and the compiler didn't fall over! But it will now. 🤯")
|
||||
} else if union_tags.len() > u32::MAX as _ {
|
||||
(IntLitWidth::U64, Variable::UNSIGNED64, Symbol::HASH_ADD_U64)
|
||||
} else if union_tags.len() > u16::MAX as _ {
|
||||
(IntLitWidth::U32, Variable::UNSIGNED32, Symbol::HASH_ADD_U32)
|
||||
} else if union_tags.len() > u8::MAX as _ {
|
||||
(IntLitWidth::U16, Variable::UNSIGNED16, Symbol::HASH_ADD_U16)
|
||||
} else {
|
||||
(IntLitWidth::U8, Variable::UNSIGNED8, Symbol::HASH_ADD_U8)
|
||||
};
|
||||
let discr_num_var = int_lit_width_to_variable(discr_width);
|
||||
|
||||
// Build the branches of the body
|
||||
let whole_hasher_var = env.subs.fresh_unnamed_flex_var();
|
||||
let branches = union_tags
|
||||
.iter_all()
|
||||
.enumerate()
|
||||
.map(|(discr_n, (tag, payloads))| {
|
||||
// A
|
||||
let tag_name = env.subs[tag].clone();
|
||||
// t11 .. t1n
|
||||
let payload_vars = env.subs.get_subs_slice(env.subs[payloads]).to_vec();
|
||||
// x11 .. x1n
|
||||
let payload_syms: Vec<_> = std::iter::repeat_with(|| env.unique_symbol())
|
||||
.take(payload_vars.len())
|
||||
.collect();
|
||||
|
||||
// `A x1 .. x1n` pattern
|
||||
let pattern = Pattern::AppliedTag {
|
||||
whole_var: union_var,
|
||||
tag_name,
|
||||
ext_var: Variable::EMPTY_TAG_UNION,
|
||||
// (t1, v1) (t2, v2)
|
||||
arguments: (payload_vars.iter())
|
||||
.zip(payload_syms.iter())
|
||||
.map(|(var, sym)| (*var, Loc::at_zero(Pattern::Identifier(*sym))))
|
||||
.collect(),
|
||||
};
|
||||
let branch_pattern = WhenBranchPattern {
|
||||
pattern: Loc::at_zero(pattern),
|
||||
degenerate: false,
|
||||
};
|
||||
|
||||
// discrHasher = (Hash.uN hasher n)
|
||||
let (discr_hasher_var, disc_hasher_expr) = call_hash_ability_member(
|
||||
env,
|
||||
hash_discr_member,
|
||||
(hasher_var, Expr::Var(hasher_sym)),
|
||||
(
|
||||
discr_num_var,
|
||||
Expr::Int(
|
||||
discr_num_var,
|
||||
discr_precision_var,
|
||||
format!("{}", discr_n).into_boxed_str(),
|
||||
IntValue::I128((discr_n as i128).to_ne_bytes()),
|
||||
IntBound::Exact(discr_width),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Fold up `Hash.hash (... (Hash.hash discrHasher x11) ...) x1n`
|
||||
let (body_var, body_expr) = (payload_vars.into_iter()).zip(payload_syms).fold(
|
||||
(discr_hasher_var, disc_hasher_expr),
|
||||
|total_hasher, (payload_var, payload_sym)| {
|
||||
call_hash_hash(env, total_hasher, (payload_var, Expr::Var(payload_sym)))
|
||||
},
|
||||
);
|
||||
|
||||
env.unify(whole_hasher_var, body_var);
|
||||
|
||||
WhenBranch {
|
||||
patterns: vec![branch_pattern],
|
||||
value: Loc::at_zero(body_expr),
|
||||
guard: None,
|
||||
redundant: RedundantMark::known_non_redundant(),
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// when union is
|
||||
// ...
|
||||
let when_var = whole_hasher_var;
|
||||
let when_expr = Expr::When {
|
||||
loc_cond: Box::new(Loc::at_zero(Expr::Var(union_sym))),
|
||||
cond_var: union_var,
|
||||
expr_var: when_var,
|
||||
region: Region::zero(),
|
||||
branches,
|
||||
branches_cond_var: union_var,
|
||||
exhaustive: ExhaustiveMark::known_exhaustive(),
|
||||
};
|
||||
|
||||
// Finally, build the closure
|
||||
// \hasher, rcd -> body
|
||||
build_outer_derived_closure(
|
||||
env,
|
||||
fn_name,
|
||||
(hasher_var, hasher_sym),
|
||||
(union_var, Pattern::Identifier(union_sym)),
|
||||
(when_var, when_expr),
|
||||
)
|
||||
}
|
||||
|
||||
/// Build a `hash` implementation for a newtype (singleton) tag union.
|
||||
/// If a tag union is a newtype, we do not need to hash its discriminant.
|
||||
fn hash_newtype_tag_union(
|
||||
env: &mut Env<'_>,
|
||||
fn_name: Symbol,
|
||||
tag: (TagName, u16),
|
||||
) -> (Variable, Expr) {
|
||||
// Suppose tags = [ A p1 .. pn ]
|
||||
// Build a generalized type t_tags = [ A t1 .. tn ],
|
||||
// with fresh t1, ..., tn, so that we can re-use the derived impl for many
|
||||
// unions of the same tag and payload arity.
|
||||
let (union_var, tag_name, payload_variables) = {
|
||||
let (label, arity) = tag;
|
||||
|
||||
let variables_slice = VariableSubsSlice::reserve_into_subs(env.subs, arity.into());
|
||||
for var_index in variables_slice {
|
||||
env.subs[var_index] = env.subs.fresh_unnamed_flex_var();
|
||||
}
|
||||
|
||||
let variables_slices_slice =
|
||||
SubsSlice::extend_new(&mut env.subs.variable_slices, [variables_slice]);
|
||||
let tag_name_index = SubsIndex::push_new(&mut env.subs.tag_names, label.clone());
|
||||
|
||||
let union_tags = UnionTags::from_slices(tag_name_index.as_slice(), variables_slices_slice);
|
||||
let tag_union_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::TagUnion(union_tags, Variable::EMPTY_TAG_UNION)),
|
||||
);
|
||||
|
||||
(
|
||||
tag_union_var,
|
||||
label,
|
||||
env.subs.get_subs_slice(variables_slice).to_vec(),
|
||||
)
|
||||
};
|
||||
|
||||
// Now, a hasher for this tag union is
|
||||
//
|
||||
// hash_union : hasher, [ A t1 .. tn ] -> hasher | hasher has Hasher
|
||||
// hash_union = \hasher, A x1 .. xn ->
|
||||
// Hash.hash (... (Hash.hash discrHasher x1) ...) xn
|
||||
let hasher_sym = env.new_symbol("hasher");
|
||||
let hasher_var = synth_var(env.subs, Content::FlexAbleVar(None, Symbol::HASH_HASHER));
|
||||
|
||||
// A
|
||||
let tag_name = tag_name;
|
||||
// t1 .. tn
|
||||
let payload_vars = payload_variables;
|
||||
// x11 .. x1n
|
||||
let payload_syms: Vec<_> = std::iter::repeat_with(|| env.unique_symbol())
|
||||
.take(payload_vars.len())
|
||||
.collect();
|
||||
|
||||
// `A x1 .. x1n` pattern
|
||||
let pattern = Pattern::AppliedTag {
|
||||
whole_var: union_var,
|
||||
tag_name,
|
||||
ext_var: Variable::EMPTY_TAG_UNION,
|
||||
// (t1, v1) (t2, v2)
|
||||
arguments: (payload_vars.iter())
|
||||
.zip(payload_syms.iter())
|
||||
.map(|(var, sym)| (*var, Loc::at_zero(Pattern::Identifier(*sym))))
|
||||
.collect(),
|
||||
};
|
||||
|
||||
// Fold up `Hash.hash (... (Hash.hash discrHasher x11) ...) x1n`
|
||||
let (body_var, body_expr) = (payload_vars.into_iter()).zip(payload_syms).fold(
|
||||
(hasher_var, Expr::Var(hasher_sym)),
|
||||
|total_hasher, (payload_var, payload_sym)| {
|
||||
call_hash_hash(env, total_hasher, (payload_var, Expr::Var(payload_sym)))
|
||||
},
|
||||
);
|
||||
|
||||
// Finally, build the closure
|
||||
// \hasher, rcd -> body
|
||||
build_outer_derived_closure(
|
||||
env,
|
||||
fn_name,
|
||||
(hasher_var, hasher_sym),
|
||||
(union_var, pattern),
|
||||
(body_var, body_expr),
|
||||
)
|
||||
}
|
||||
|
||||
fn call_hash_hash(
|
||||
env: &mut Env<'_>,
|
||||
hasher: (Variable, Expr),
|
||||
val: (Variable, Expr),
|
||||
) -> (Variable, Expr) {
|
||||
call_hash_ability_member(env, Symbol::HASH_HASH, hasher, val)
|
||||
}
|
||||
|
||||
fn call_hash_ability_member(
|
||||
env: &mut Env<'_>,
|
||||
member: Symbol,
|
||||
hasher: (Variable, Expr),
|
||||
val: (Variable, Expr),
|
||||
) -> (Variable, Expr) {
|
||||
let (in_hasher_var, in_hasher_expr) = hasher;
|
||||
let (in_val_var, in_val_expr) = val;
|
||||
|
||||
// build `member ...` function type. `member` here is `Hash.hash` or `Hash.addU16`.
|
||||
//
|
||||
// hasher, val -[uls]-> hasher | hasher has Hasher, val has Hash
|
||||
let exposed_hash_fn_var = env.import_builtin_symbol_var(member);
|
||||
|
||||
// (typeof body), (typeof field) -[clos]-> hasher_result
|
||||
let this_arguments_slice =
|
||||
VariableSubsSlice::insert_into_subs(env.subs, [in_hasher_var, in_val_var]);
|
||||
let this_hash_clos_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_out_hasher_var = env.subs.fresh_unnamed_flex_var();
|
||||
let this_hash_fn_var = synth_var(
|
||||
env.subs,
|
||||
Content::Structure(FlatType::Func(
|
||||
this_arguments_slice,
|
||||
this_hash_clos_var,
|
||||
this_out_hasher_var,
|
||||
)),
|
||||
);
|
||||
|
||||
// hasher, val -[uls]-> hasher | hasher has Hasher, val has Hash
|
||||
// ~ (typeof body), (typeof field) -[clos]-> hasher_result
|
||||
env.unify(exposed_hash_fn_var, this_hash_fn_var);
|
||||
|
||||
// Hash.hash : hasher, (typeof field) -[clos]-> hasher | hasher has Hasher, (typeof field) has Hash
|
||||
let hash_fn_head = Expr::AbilityMember(member, None, this_hash_fn_var);
|
||||
let hash_fn_data = Box::new((
|
||||
this_hash_fn_var,
|
||||
Loc::at_zero(hash_fn_head),
|
||||
this_hash_clos_var,
|
||||
this_out_hasher_var,
|
||||
));
|
||||
|
||||
let hash_arguments = vec![
|
||||
(in_hasher_var, Loc::at_zero(in_hasher_expr)),
|
||||
(in_val_var, Loc::at_zero(in_val_expr)),
|
||||
];
|
||||
let call_hash = Expr::Call(hash_fn_data, hash_arguments, CalledVia::Space);
|
||||
|
||||
(this_out_hasher_var, call_hash)
|
||||
}
|
||||
|
||||
fn build_outer_derived_closure(
|
||||
env: &mut Env<'_>,
|
||||
fn_name: Symbol,
|
||||
hasher: (Variable, Symbol),
|
||||
val: (Variable, Pattern),
|
||||
body: (Variable, Expr),
|
||||
) -> (Variable, Expr) {
|
||||
let (hasher_var, hasher_sym) = hasher;
|
||||
let (val_var, val_pattern) = val;
|
||||
let (body_var, body_expr) = body;
|
||||
|
||||
let (fn_var, fn_clos_var) = {
|
||||
// Create fn_var for ambient capture; we fix it up below.
|
||||
@ -156,7 +442,7 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (E
|
||||
);
|
||||
|
||||
// hasher, rcd_var -[fn_name]-> (hasher = body_var)
|
||||
let args_slice = SubsSlice::insert_into_subs(env.subs, [hasher_var, record_var]);
|
||||
let args_slice = SubsSlice::insert_into_subs(env.subs, [hasher_var, val_var]);
|
||||
env.subs.set_content(
|
||||
fn_var,
|
||||
Content::Structure(FlatType::Func(args_slice, fn_clos_var, body_var)),
|
||||
@ -179,13 +465,13 @@ fn hash_record(env: &mut Env<'_>, fn_name: Symbol, fields: Vec<Lowercase>) -> (E
|
||||
Loc::at_zero(Pattern::Identifier(hasher_sym)),
|
||||
),
|
||||
(
|
||||
record_var,
|
||||
val_var,
|
||||
AnnotatedMark::known_exhaustive(),
|
||||
Loc::at_zero(Pattern::Identifier(rcd_sym)),
|
||||
Loc::at_zero(val_pattern),
|
||||
),
|
||||
],
|
||||
loc_body: Box::new(Loc::at_zero(body)),
|
||||
loc_body: Box::new(Loc::at_zero(body_expr)),
|
||||
});
|
||||
|
||||
(clos_expr, fn_var)
|
||||
(fn_var, clos_expr)
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ use roc_module::{
|
||||
use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable};
|
||||
|
||||
use crate::{
|
||||
util::{check_derivable_ext_var, debug_name_record},
|
||||
util::{check_derivable_ext_var, debug_name_record, debug_name_tag},
|
||||
DeriveError,
|
||||
};
|
||||
|
||||
@ -32,19 +32,7 @@ impl FlatEncodableKey {
|
||||
FlatEncodableKey::Set() => "set".to_string(),
|
||||
FlatEncodableKey::Dict() => "dict".to_string(),
|
||||
FlatEncodableKey::Record(fields) => debug_name_record(fields),
|
||||
FlatEncodableKey::TagUnion(tags) => {
|
||||
let mut str = String::from('[');
|
||||
tags.iter().enumerate().for_each(|(i, (tag, arity))| {
|
||||
if i > 0 {
|
||||
str.push(',');
|
||||
}
|
||||
str.push_str(tag.0.as_str());
|
||||
str.push(' ');
|
||||
str.push_str(&arity.to_string());
|
||||
});
|
||||
str.push(']');
|
||||
str
|
||||
}
|
||||
FlatEncodableKey::TagUnion(tags) => debug_name_tag(tags),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,11 @@
|
||||
use roc_module::{ident::Lowercase, symbol::Symbol};
|
||||
use roc_module::{
|
||||
ident::{Lowercase, TagName},
|
||||
symbol::Symbol,
|
||||
};
|
||||
use roc_types::subs::{Content, FlatType, Subs, Variable};
|
||||
|
||||
use crate::{
|
||||
util::{check_derivable_ext_var, debug_name_record},
|
||||
util::{check_derivable_ext_var, debug_name_record, debug_name_tag},
|
||||
DeriveError,
|
||||
};
|
||||
|
||||
@ -18,12 +21,14 @@ pub enum FlatHash {
|
||||
pub enum FlatHashKey {
|
||||
// Unfortunate that we must allocate here, c'est la vie
|
||||
Record(Vec<Lowercase>),
|
||||
TagUnion(Vec<(TagName, u16)>),
|
||||
}
|
||||
|
||||
impl FlatHashKey {
|
||||
pub(crate) fn debug_name(&self) -> String {
|
||||
match self {
|
||||
FlatHashKey::Record(fields) => debug_name_record(fields),
|
||||
FlatHashKey::TagUnion(tags) => debug_name_tag(tags),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -60,16 +65,40 @@ impl FlatHash {
|
||||
|
||||
Ok(Key(FlatHashKey::Record(field_names)))
|
||||
}
|
||||
FlatType::TagUnion(_tags, _ext) | FlatType::RecursiveTagUnion(_, _tags, _ext) => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::FunctionOrTagUnion(_name_index, _, _) => {
|
||||
Err(Underivable) // yet
|
||||
FlatType::TagUnion(tags, ext) | FlatType::RecursiveTagUnion(_, tags, ext) => {
|
||||
// The recursion var doesn't matter, because the derived implementation will only
|
||||
// look on the surface of the tag union type, and more over the payloads of the
|
||||
// arguments will be left generic for the monomorphizer to fill in with the
|
||||
// appropriate type. That is,
|
||||
// [ A t1, B t1 t2 ]
|
||||
// and
|
||||
// [ A t1, B t1 t2 ] as R
|
||||
// look the same on the surface, because `R` is only somewhere inside of the
|
||||
// `t`-prefixed payload types.
|
||||
let (tags_iter, ext) = tags.unsorted_tags_and_ext(subs, ext);
|
||||
|
||||
check_derivable_ext_var(subs, ext, |ext| {
|
||||
matches!(ext, Content::Structure(FlatType::EmptyTagUnion))
|
||||
})?;
|
||||
|
||||
let mut tag_names_and_payload_sizes: Vec<_> = tags_iter
|
||||
.tags
|
||||
.into_iter()
|
||||
.map(|(name, payload_slice)| {
|
||||
let payload_size = payload_slice.len();
|
||||
(name.clone(), payload_size as _)
|
||||
})
|
||||
.collect();
|
||||
|
||||
tag_names_and_payload_sizes.sort_by(|(t1, _), (t2, _)| t1.cmp(t2));
|
||||
|
||||
Ok(Key(FlatHashKey::TagUnion(tag_names_and_payload_sizes)))
|
||||
}
|
||||
FlatType::FunctionOrTagUnion(name_index, _, _) => Ok(Key(FlatHashKey::TagUnion(
|
||||
vec![(subs[name_index].clone(), 0)],
|
||||
))),
|
||||
FlatType::EmptyRecord => Ok(Key(FlatHashKey::Record(vec![]))),
|
||||
FlatType::EmptyTagUnion => {
|
||||
Err(Underivable) // yet
|
||||
}
|
||||
FlatType::EmptyTagUnion => Ok(Key(FlatHashKey::TagUnion(vec![]))),
|
||||
//
|
||||
FlatType::Erroneous(_) => Err(Underivable),
|
||||
FlatType::Func(..) => Err(Underivable),
|
||||
@ -111,7 +140,8 @@ impl FlatHash {
|
||||
},
|
||||
Content::RangedNumber(_) => Err(Underivable),
|
||||
//
|
||||
Content::RecursionVar { .. } => Err(Underivable),
|
||||
Content::RecursionVar { structure, .. } => Self::from_var(subs, structure),
|
||||
//
|
||||
Content::Error => Err(Underivable),
|
||||
Content::FlexVar(_)
|
||||
| Content::RigidVar(_)
|
||||
|
@ -1,4 +1,4 @@
|
||||
use roc_module::ident::Lowercase;
|
||||
use roc_module::ident::{Lowercase, TagName};
|
||||
use roc_types::subs::{Content, Subs, Variable};
|
||||
|
||||
use crate::DeriveError;
|
||||
@ -42,3 +42,17 @@ pub(crate) fn debug_name_record(fields: &[Lowercase]) -> String {
|
||||
str.push('}');
|
||||
str
|
||||
}
|
||||
|
||||
pub(crate) fn debug_name_tag(tags: &[(TagName, u16)]) -> String {
|
||||
let mut str = String::from('[');
|
||||
tags.iter().enumerate().for_each(|(i, (tag, arity))| {
|
||||
if i > 0 {
|
||||
str.push(',');
|
||||
}
|
||||
str.push_str(tag.0.as_str());
|
||||
str.push(' ');
|
||||
str.push_str(&arity.to_string());
|
||||
});
|
||||
str.push(']');
|
||||
str
|
||||
}
|
||||
|
@ -50,6 +50,7 @@ impl From<Layout<'_>> for CodeGenNumType {
|
||||
|| internal_error!("Tried to perform a Num low-level operation on {:?}", layout);
|
||||
match layout {
|
||||
Layout::Builtin(builtin) => match builtin {
|
||||
Builtin::Bool => I32,
|
||||
Builtin::Int(int_width) => match int_width {
|
||||
IntWidth::U8 => I32,
|
||||
IntWidth::U16 => I32,
|
||||
@ -1724,6 +1725,7 @@ impl<'a> LowLevelCall<'a> {
|
||||
let arg_type = CodeGenNumType::from(arg_layout);
|
||||
let arg_width = match arg_layout {
|
||||
Layout::Builtin(Builtin::Int(w)) => w,
|
||||
Layout::Builtin(Builtin::Bool) => IntWidth::U8,
|
||||
x => internal_error!("Num.intCast is not defined for {:?}", x),
|
||||
};
|
||||
|
||||
|
@ -217,7 +217,6 @@ macro_rules! map_symbol_to_lowlevel {
|
||||
LowLevel::StrFromInt => unimplemented!(),
|
||||
LowLevel::StrFromFloat => unimplemented!(),
|
||||
LowLevel::NumIsFinite => unimplemented!(),
|
||||
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -879,15 +879,8 @@ impl<'a> UnionLayout<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn tag_id_layout(&self) -> Layout<'a> {
|
||||
// TODO is it beneficial to return a more specific layout?
|
||||
// e.g. Layout::bool() and Layout::VOID
|
||||
match self.discriminant() {
|
||||
Discriminant::U0 => Layout::u8(),
|
||||
Discriminant::U1 => Layout::u8(),
|
||||
Discriminant::U8 => Layout::u8(),
|
||||
Discriminant::U16 => Layout::u16(),
|
||||
}
|
||||
pub fn tag_id_layout(&self) -> Layout<'static> {
|
||||
self.discriminant().layout()
|
||||
}
|
||||
|
||||
fn stores_tag_id_in_pointer_bits(tags: &[&[Layout<'a>]], target_info: TargetInfo) -> bool {
|
||||
@ -1138,6 +1131,17 @@ impl Discriminant {
|
||||
pub const fn alignment_bytes(&self) -> u32 {
|
||||
self.stack_size()
|
||||
}
|
||||
|
||||
pub const fn layout(&self) -> Layout<'static> {
|
||||
// TODO is it beneficial to return a more specific layout?
|
||||
// e.g. Layout::bool() and Layout::VOID
|
||||
match self {
|
||||
Discriminant::U0 => Layout::u8(),
|
||||
Discriminant::U1 => Layout::u8(),
|
||||
Discriminant::U8 => Layout::u8(),
|
||||
Discriminant::U16 => Layout::u16(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Custom type so we can get the numeric representation of a symbol in tests (so `#UserApp.3`
|
||||
@ -2713,7 +2717,7 @@ impl<'a> Layout<'a> {
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U8))
|
||||
}
|
||||
|
||||
pub fn u16() -> Layout<'a> {
|
||||
pub const fn u16() -> Layout<'a> {
|
||||
Layout::Builtin(Builtin::Int(IntWidth::U16))
|
||||
}
|
||||
|
||||
|
@ -655,7 +655,7 @@ fn make_specialization_decision<P: Phase>(
|
||||
})
|
||||
}
|
||||
}
|
||||
Structure(_) | Alias(_, _, _, _) => {
|
||||
Structure(_) | Alias(_, _, _, _) | RecursionVar { .. } => {
|
||||
let builtin = match ability_member.try_into() {
|
||||
Ok(builtin) => builtin,
|
||||
Err(_) => return SpecializeDecision::Drop,
|
||||
@ -691,7 +691,6 @@ fn make_specialization_decision<P: Phase>(
|
||||
| RigidAbleVar(..)
|
||||
| FlexVar(..)
|
||||
| RigidVar(..)
|
||||
| RecursionVar { .. }
|
||||
| LambdaSet(..)
|
||||
| RangedNumber(..) => {
|
||||
internal_error!("unexpected")
|
||||
|
@ -294,7 +294,8 @@ fn tag_one_label_zero_args() {
|
||||
\#Derived.bytes, #Derived.fmt ->
|
||||
Encode.appendWith
|
||||
#Derived.bytes
|
||||
(when #Derived.tag is A -> Encode.tag "A" [])
|
||||
(when #Derived.tag is
|
||||
A -> Encode.tag "A" [])
|
||||
#Derived.fmt
|
||||
"###
|
||||
)
|
||||
|
@ -27,6 +27,20 @@ test_key_eq! {
|
||||
v!({ c: v!(U8), a: v!(U8), b: v!(U8), })
|
||||
explicit_empty_record_and_implicit_empty_record:
|
||||
v!(EMPTY_RECORD), v!({})
|
||||
|
||||
same_tag_union:
|
||||
v!([ A v!(U8) v!(STR), B v!(STR) ]), v!([ A v!(U8) v!(STR), B v!(STR) ])
|
||||
same_tag_union_tags_diff_types:
|
||||
v!([ A v!(U8) v!(U8), B v!(U8) ]), v!([ A v!(STR) v!(STR), B v!(STR) ])
|
||||
same_tag_union_tags_any_order:
|
||||
v!([ A v!(U8) v!(U8), B v!(U8), C ]), v!([ C, B v!(STR), A v!(STR) v!(STR) ])
|
||||
explicit_empty_tag_union_and_implicit_empty_tag_union:
|
||||
v!(EMPTY_TAG_UNION), v!([])
|
||||
|
||||
same_recursive_tag_union:
|
||||
v!([ Nil, Cons v!(^lst)] as lst), v!([ Nil, Cons v!(^lst)] as lst)
|
||||
same_tag_union_and_recursive_tag_union_fields:
|
||||
v!([ Nil, Cons v!(STR)]), v!([ Nil, Cons v!(^lst)] as lst)
|
||||
}
|
||||
|
||||
test_key_neq! {
|
||||
@ -36,6 +50,13 @@ test_key_neq! {
|
||||
v!({ a: v!(U8), }), v!({ b: v!(U8), })
|
||||
record_empty_vs_nonempty:
|
||||
v!(EMPTY_RECORD), v!({ a: v!(U8), })
|
||||
|
||||
different_tag_union_tags:
|
||||
v!([ A v!(U8) ]), v!([ B v!(U8) ])
|
||||
tag_union_empty_vs_nonempty:
|
||||
v!(EMPTY_TAG_UNION), v!([ B v!(U8) ])
|
||||
different_recursive_tag_union_tags:
|
||||
v!([ Nil, Cons v!(^lst) ] as lst), v!([ Nil, Next v!(^lst) ] as lst)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -87,6 +108,36 @@ fn derivable_record_with_record_ext() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derivable_tag_ext_flex_var() {
|
||||
check_derivable(
|
||||
Hash,
|
||||
v!([ A v!(STR) ]* ),
|
||||
DeriveKey::Hash(FlatHashKey::TagUnion(vec![("A".into(), 1)])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derivable_tag_ext_flex_able_var() {
|
||||
check_derivable(
|
||||
Hash,
|
||||
v!([ A v!(STR) ]a has Symbol::ENCODE_TO_ENCODER),
|
||||
DeriveKey::Hash(FlatHashKey::TagUnion(vec![("A".into(), 1)])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derivable_tag_with_tag_ext() {
|
||||
check_derivable(
|
||||
Hash,
|
||||
v!([ B v!(STR) v!(U8) ][ A v!(STR) ]),
|
||||
DeriveKey::Hash(FlatHashKey::TagUnion(vec![
|
||||
("A".into(), 1),
|
||||
("B".into(), 2),
|
||||
])),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_record() {
|
||||
derive_test(Hash, v!(EMPTY_RECORD), |golden| {
|
||||
@ -149,3 +200,100 @@ fn two_field_record() {
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_one_label_no_payloads() {
|
||||
derive_test(Hash, v!([A]), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [A]
|
||||
# hasher, [A] -[[hash_[A 0](0)]]-> hasher | hasher has Hasher
|
||||
# hasher, [A] -[[hash_[A 0](0)]]-> hasher | hasher has Hasher
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_[A 0](0)]]
|
||||
#Derived.hash_[A 0] = \#Derived.hasher, A -> #Derived.hasher
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_one_label_newtype() {
|
||||
derive_test(Hash, v!([A v!(U8) v!(STR)]), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [A U8 Str]
|
||||
# hasher, [A a a1] -[[hash_[A 2](0)]]-> hasher | a has Hash, a1 has Hash, hasher has Hasher
|
||||
# hasher, [A a a1] -[[hash_[A 2](0)]]-> hasher | a has Hash, a1 has Hash, hasher has Hasher
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_[A 2](0)]]
|
||||
#Derived.hash_[A 2] =
|
||||
\#Derived.hasher, A #Derived.2 #Derived.3 ->
|
||||
Hash.hash (Hash.hash #Derived.hasher #Derived.2) #Derived.3
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_two_labels() {
|
||||
derive_test(Hash, v!([A v!(U8) v!(STR) v!(U16), B v!(STR)]), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [A U8 Str U16, B Str]
|
||||
# a, [A a1 a2 a3, B a3] -[[hash_[A 3,B 1](0)]]-> a | a has Hasher, a1 has Hash, a2 has Hash, a3 has Hash
|
||||
# a, [A a1 a2 a3, B a3] -[[hash_[A 3,B 1](0)]]-> a | a has Hasher, a1 has Hash, a2 has Hash, a3 has Hash
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_[A 3,B 1](0)]]
|
||||
#Derived.hash_[A 3,B 1] =
|
||||
\#Derived.hasher, #Derived.union ->
|
||||
when #Derived.union is
|
||||
A #Derived.3 #Derived.4 #Derived.5 ->
|
||||
Hash.hash
|
||||
(Hash.hash
|
||||
(Hash.hash (Hash.addU8 #Derived.hasher 0) #Derived.3)
|
||||
#Derived.4)
|
||||
#Derived.5
|
||||
B #Derived.6 -> Hash.hash (Hash.addU8 #Derived.hasher 1) #Derived.6
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tag_two_labels_no_payloads() {
|
||||
derive_test(Hash, v!([A, B]), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [A, B]
|
||||
# a, [A, B] -[[hash_[A 0,B 0](0)]]-> a | a has Hasher
|
||||
# a, [A, B] -[[hash_[A 0,B 0](0)]]-> a | a has Hasher
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_[A 0,B 0](0)]]
|
||||
#Derived.hash_[A 0,B 0] =
|
||||
\#Derived.hasher, #Derived.union ->
|
||||
when #Derived.union is
|
||||
A -> Hash.addU8 #Derived.hasher 0
|
||||
B -> Hash.addU8 #Derived.hasher 1
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn recursive_tag_union() {
|
||||
derive_test(Hash, v!([Nil, Cons v!(U8) v!(^lst) ] as lst), |golden| {
|
||||
assert_snapshot!(golden, @r###"
|
||||
# derived for [Cons U8 $rec, Nil] as $rec
|
||||
# a, [Cons a1 a2, Nil] -[[hash_[Cons 2,Nil 0](0)]]-> a | a has Hasher, a1 has Hash, a2 has Hash
|
||||
# a, [Cons a1 a2, Nil] -[[hash_[Cons 2,Nil 0](0)]]-> a | a has Hasher, a1 has Hash, a2 has Hash
|
||||
# Specialization lambda sets:
|
||||
# @<1>: [[hash_[Cons 2,Nil 0](0)]]
|
||||
#Derived.hash_[Cons 2,Nil 0] =
|
||||
\#Derived.hasher, #Derived.union ->
|
||||
when #Derived.union is
|
||||
Cons #Derived.3 #Derived.4 ->
|
||||
Hash.hash
|
||||
(Hash.hash (Hash.addU8 #Derived.hasher 0) #Derived.3)
|
||||
#Derived.4
|
||||
Nil -> Hash.addU8 #Derived.hasher 1
|
||||
"###
|
||||
)
|
||||
})
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ use roc_can::expr::{ClosureData, OpaqueWrapFunctionData, WhenBranch};
|
||||
use roc_can::pattern::{Pattern, RecordDestruct};
|
||||
|
||||
use roc_module::symbol::Interns;
|
||||
|
||||
use ven_pretty::{Arena, DocAllocator, DocBuilder};
|
||||
|
||||
pub struct Ctx<'a> {
|
||||
@ -100,8 +101,12 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
||||
.append(expr(c, Free, f, &loc_cond.value))
|
||||
.append(f.text(" is"))
|
||||
.append(
|
||||
f.concat(branches.iter().map(|b| f.line().append(branch(c, f, b))))
|
||||
.group(),
|
||||
f.concat(
|
||||
branches
|
||||
.iter()
|
||||
.map(|b| f.hardline().append(branch(c, f, b)))
|
||||
)
|
||||
.group(),
|
||||
)
|
||||
.nest(2)
|
||||
.group()
|
||||
@ -134,7 +139,10 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
||||
)
|
||||
.group(),
|
||||
LetRec(_, _, _) => todo!(),
|
||||
LetNonRec(_, _) => todo!(),
|
||||
LetNonRec(loc_def, body) => def(c, f, loc_def)
|
||||
.append(f.hardline())
|
||||
.append(expr(c, Free, f, &body.value))
|
||||
.group(),
|
||||
Call(fun, args, _) => {
|
||||
let (_, fun, _, _) = &**fun;
|
||||
maybe_paren!(
|
||||
@ -154,7 +162,24 @@ fn expr<'a>(c: &Ctx, p: EPrec, f: &'a Arena<'a>, e: &'a Expr) -> DocBuilder<'a,
|
||||
.nest(2)
|
||||
)
|
||||
}
|
||||
RunLowLevel { .. } => todo!(),
|
||||
RunLowLevel { args, .. } => {
|
||||
let op = "LowLevel";
|
||||
|
||||
maybe_paren!(
|
||||
Free,
|
||||
p,
|
||||
f.reflow(op)
|
||||
.append(
|
||||
f.concat(
|
||||
args.iter()
|
||||
.map(|le| f.line().append(expr(c, AppArg, f, &le.1)))
|
||||
)
|
||||
.group()
|
||||
)
|
||||
.group()
|
||||
.nest(2)
|
||||
)
|
||||
}
|
||||
ForeignCall { .. } => todo!(),
|
||||
Closure(ClosureData {
|
||||
arguments,
|
||||
|
@ -1332,7 +1332,7 @@ mod hash {
|
||||
}
|
||||
|
||||
mod derived {
|
||||
use super::{assert_evals_to, build_test};
|
||||
use super::{assert_evals_to, build_test, indoc, TEST_HASHER};
|
||||
use roc_std::RocList;
|
||||
|
||||
#[test]
|
||||
@ -1372,5 +1372,233 @@ mod hash {
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_singleton_union() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
a : [A]
|
||||
a = A
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash a
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
// hash nothing because this is a newtype of a unit layout.
|
||||
] as &[u8]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_bool_tag_union() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
a : [A, B]
|
||||
a = A
|
||||
|
||||
b : [A, B]
|
||||
b = B
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash a
|
||||
|> Hash.hash b
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
0, // A
|
||||
1, // B
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_byte_tag_union() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
l : List [A, B, C, D, E, F, G, H]
|
||||
l = [A, B, C, D, E, F, G, H]
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash l
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
0, // A
|
||||
1, // B
|
||||
2, // C
|
||||
3, // D
|
||||
4, // E
|
||||
5, // F
|
||||
6, // G
|
||||
7, // H
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_newtype_tag_union() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
a : [A U8 U8 U8]
|
||||
a = A 15 23 47
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash a
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
// discriminant is skipped because it's a newtype
|
||||
15, 23, 47
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_newtype_by_void_tag_union() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
a : Result [A U8 U8 U8] []
|
||||
a = Ok (A 15 23 47)
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash a
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
1, // Ok
|
||||
// A is skipped because it is a newtype
|
||||
15, 23, 47
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_heterogenous_tags() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
a : [A U8 U8, B {{ a: U8 }}, C Str]
|
||||
a = A 15 23
|
||||
|
||||
b : [A U8 U8, B {{ a: U8 }}, C Str]
|
||||
b = B {{ a: 37 }}
|
||||
|
||||
c : [A U8 U8, B {{ a: U8 }}, C Str]
|
||||
c = C "abc"
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash a
|
||||
|> Hash.hash b
|
||||
|> Hash.hash c
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
0, // dicsr A
|
||||
15, 23, // payloads A
|
||||
1, // discr B
|
||||
37, // payloads B
|
||||
2, // discr C
|
||||
97, 98, 99 // payloads C
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hash_recursive_tag_union() {
|
||||
assert_evals_to!(
|
||||
&format!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
{}
|
||||
|
||||
ConsList : [Cons U8 ConsList, Nil]
|
||||
|
||||
c : ConsList
|
||||
c = Cons 1 (Cons 2 Nil)
|
||||
|
||||
main =
|
||||
@THasher []
|
||||
|> Hash.hash c
|
||||
|> tRead
|
||||
"#
|
||||
),
|
||||
TEST_HASHER,
|
||||
),
|
||||
RocList::from_slice(&[
|
||||
0, 1, // Cons 1
|
||||
0, 2, // Cons 2
|
||||
1, // Nil
|
||||
]),
|
||||
RocList<u8>
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2948,7 +2948,6 @@ fn unify_flex_able<M: MetaCollector>(
|
||||
}
|
||||
|
||||
RigidVar(_) => mismatch!("FlexAble can never unify with non-able Rigid"),
|
||||
RecursionVar { .. } => mismatch!("FlexAble with RecursionVar"),
|
||||
LambdaSet(..) => mismatch!("FlexAble with LambdaSet"),
|
||||
|
||||
Alias(name, _args, _real_var, AliasKind::Opaque) => {
|
||||
@ -2963,7 +2962,10 @@ fn unify_flex_able<M: MetaCollector>(
|
||||
)
|
||||
}
|
||||
|
||||
Structure(_) | Alias(_, _, _, AliasKind::Structural) | RangedNumber(..) => {
|
||||
RecursionVar { .. }
|
||||
| Structure(_)
|
||||
| Alias(_, _, _, AliasKind::Structural)
|
||||
| RangedNumber(..) => {
|
||||
// Structural type wins.
|
||||
merge_flex_able_with_concrete(
|
||||
env,
|
||||
@ -3046,10 +3048,22 @@ fn unify_recursion<M: MetaCollector>(
|
||||
mismatch!("RecursionVar {:?} with rigid {:?}", ctx.first, &other)
|
||||
}
|
||||
|
||||
FlexAbleVar(..) | RigidAbleVar(..) => {
|
||||
RigidAbleVar(..) => {
|
||||
mismatch!("RecursionVar {:?} with able var {:?}", ctx.first, &other)
|
||||
}
|
||||
|
||||
FlexAbleVar(_, ability) => merge_flex_able_with_concrete(
|
||||
env,
|
||||
ctx,
|
||||
ctx.second,
|
||||
*ability,
|
||||
RecursionVar {
|
||||
structure,
|
||||
opt_name: *opt_name,
|
||||
},
|
||||
Obligated::Adhoc(ctx.first),
|
||||
),
|
||||
|
||||
FlexVar(_) => merge(
|
||||
env,
|
||||
ctx,
|
||||
|
Loading…
Reference in New Issue
Block a user