mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
Add support for deriving Hash for opaques
This commit is contained in:
parent
a4ed5a582d
commit
40e05d5a00
3 changed files with 213 additions and 22 deletions
|
@ -127,11 +127,7 @@ enum PendingValueDef<'a> {
|
||||||
&'a Loc<ast::TypeAnnotation<'a>>,
|
&'a Loc<ast::TypeAnnotation<'a>>,
|
||||||
),
|
),
|
||||||
/// A body with no type annotation
|
/// A body with no type annotation
|
||||||
Body(
|
Body(Loc<Pattern>, &'a Loc<ast::Expr<'a>>),
|
||||||
&'a Loc<ast::Pattern<'a>>,
|
|
||||||
Loc<Pattern>,
|
|
||||||
&'a Loc<ast::Expr<'a>>,
|
|
||||||
),
|
|
||||||
/// A body with a type annotation
|
/// A body with a type annotation
|
||||||
TypedBody(
|
TypedBody(
|
||||||
&'a Loc<ast::Pattern<'a>>,
|
&'a Loc<ast::Pattern<'a>>,
|
||||||
|
@ -145,7 +141,7 @@ impl PendingValueDef<'_> {
|
||||||
fn loc_pattern(&self) -> &Loc<Pattern> {
|
fn loc_pattern(&self) -> &Loc<Pattern> {
|
||||||
match self {
|
match self {
|
||||||
PendingValueDef::AnnotationOnly(_, loc_pattern, _) => loc_pattern,
|
PendingValueDef::AnnotationOnly(_, loc_pattern, _) => loc_pattern,
|
||||||
PendingValueDef::Body(_, loc_pattern, _) => loc_pattern,
|
PendingValueDef::Body(loc_pattern, _) => loc_pattern,
|
||||||
PendingValueDef::TypedBody(_, loc_pattern, _, _) => loc_pattern,
|
PendingValueDef::TypedBody(_, loc_pattern, _, _) => loc_pattern,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,6 +164,7 @@ enum PendingTypeDef<'a> {
|
||||||
|
|
||||||
/// An opaque type alias, e.g. `Age := U32`.
|
/// An opaque type alias, e.g. `Age := U32`.
|
||||||
Opaque {
|
Opaque {
|
||||||
|
name_str: &'a str,
|
||||||
name: Loc<Symbol>,
|
name: Loc<Symbol>,
|
||||||
vars: Vec<Loc<Lowercase>>,
|
vars: Vec<Loc<Lowercase>>,
|
||||||
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
||||||
|
@ -212,6 +209,7 @@ impl PendingTypeDef<'_> {
|
||||||
Some((name.value, region))
|
Some((name.value, region))
|
||||||
}
|
}
|
||||||
PendingTypeDef::Opaque {
|
PendingTypeDef::Opaque {
|
||||||
|
name_str: _,
|
||||||
name,
|
name,
|
||||||
vars: _,
|
vars: _,
|
||||||
ann,
|
ann,
|
||||||
|
@ -641,6 +639,109 @@ fn separate_implemented_and_required_members(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn synthesize_derived_hash<'a>(
|
||||||
|
env: &mut Env<'a>,
|
||||||
|
at_opaque: &'a str,
|
||||||
|
region: Region,
|
||||||
|
) -> ast::Expr<'a> {
|
||||||
|
let alloc_pat = |it| env.arena.alloc(Loc::at(region, it));
|
||||||
|
let alloc_expr = |it| env.arena.alloc(Loc::at(region, it));
|
||||||
|
let hasher = env.arena.alloc_str("#hasher");
|
||||||
|
|
||||||
|
let payload = env.arena.alloc_str("#payload");
|
||||||
|
|
||||||
|
// \@Opaq payload
|
||||||
|
let opaque_ref = alloc_pat(ast::Pattern::OpaqueRef(at_opaque));
|
||||||
|
let opaque_apply_pattern = ast::Pattern::Apply(
|
||||||
|
opaque_ref,
|
||||||
|
&*env
|
||||||
|
.arena
|
||||||
|
.alloc([Loc::at(region, ast::Pattern::Identifier(payload))]),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Hash.hash hasher payload
|
||||||
|
let call_member = alloc_expr(ast::Expr::Apply(
|
||||||
|
alloc_expr(ast::Expr::Var {
|
||||||
|
module_name: env.arena.alloc_str("Hash"),
|
||||||
|
ident: env.arena.alloc_str("hash"),
|
||||||
|
}),
|
||||||
|
&*env.arena.alloc([
|
||||||
|
&*alloc_expr(ast::Expr::Var {
|
||||||
|
module_name: "",
|
||||||
|
ident: hasher,
|
||||||
|
}),
|
||||||
|
&*alloc_expr(ast::Expr::Var {
|
||||||
|
module_name: "",
|
||||||
|
ident: payload,
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
roc_module::called_via::CalledVia::Space,
|
||||||
|
));
|
||||||
|
|
||||||
|
// \hasher, @Opaq payload -> Hash.hash hasher payload
|
||||||
|
ast::Expr::Closure(
|
||||||
|
env.arena.alloc([
|
||||||
|
Loc::at(region, ast::Pattern::Identifier(hasher)),
|
||||||
|
Loc::at(region, opaque_apply_pattern),
|
||||||
|
]),
|
||||||
|
call_member,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn synthesize_derived_member_impl<'a>(
|
||||||
|
env: &mut Env<'a>,
|
||||||
|
scope: &mut Scope,
|
||||||
|
opaque: Symbol,
|
||||||
|
opaque_name: &'a str,
|
||||||
|
ability_member: Symbol,
|
||||||
|
) -> (Symbol, DerivedDef<'a>) {
|
||||||
|
// @Opaq
|
||||||
|
let at_opaque = env.arena.alloc_str(&format!("@{}", opaque_name));
|
||||||
|
let region = Region::zero();
|
||||||
|
|
||||||
|
let (impl_name, def_body): (String, ast::Expr<'a>) = match ability_member {
|
||||||
|
Symbol::ENCODE_TO_ENCODER => (
|
||||||
|
format!("#{}_toEncoder", opaque_name),
|
||||||
|
todo!(),
|
||||||
|
//synthesize_derived_to_encoder(env, scope, at_opaque, region),
|
||||||
|
),
|
||||||
|
Symbol::DECODE_DECODER => (
|
||||||
|
format!("#{}_decoder", opaque_name),
|
||||||
|
todo!(),
|
||||||
|
//synthesize_derived_decoder(env, scope, at_opaque, region),
|
||||||
|
),
|
||||||
|
Symbol::HASH_HASH => (
|
||||||
|
format!("#{}_hash", opaque_name),
|
||||||
|
synthesize_derived_hash(env, at_opaque, region),
|
||||||
|
),
|
||||||
|
Symbol::BOOL_IS_EQ => (
|
||||||
|
format!("#{}_isEq", opaque_name),
|
||||||
|
todo!(),
|
||||||
|
// synthesize_derived_is_eq(env, scope, at_opaque, region),
|
||||||
|
),
|
||||||
|
other => internal_error!("{:?} is not a derivable ability member!", other),
|
||||||
|
};
|
||||||
|
|
||||||
|
let impl_symbol = scope
|
||||||
|
.introduce_str(&impl_name, region)
|
||||||
|
.expect("this name is not unique");
|
||||||
|
|
||||||
|
let def_pattern = Pattern::Identifier(impl_symbol);
|
||||||
|
|
||||||
|
let def = PendingValue::Def(PendingValueDef::Body(
|
||||||
|
Loc::at(region, def_pattern),
|
||||||
|
env.arena.alloc(Loc::at(region, def_body)),
|
||||||
|
));
|
||||||
|
(impl_symbol, Loc::at(region, def))
|
||||||
|
}
|
||||||
|
|
||||||
|
type DerivedDef<'a> = Loc<PendingValue<'a>>;
|
||||||
|
|
||||||
|
struct CanonicalizedOpaque<'a> {
|
||||||
|
opaque_def: Alias,
|
||||||
|
derived_defs: Vec<DerivedDef<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
fn canonicalize_opaque<'a>(
|
fn canonicalize_opaque<'a>(
|
||||||
|
@ -651,10 +752,11 @@ fn canonicalize_opaque<'a>(
|
||||||
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
||||||
|
|
||||||
name: Loc<Symbol>,
|
name: Loc<Symbol>,
|
||||||
|
name_str: &'a str,
|
||||||
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
||||||
vars: &[Loc<Lowercase>],
|
vars: &[Loc<Lowercase>],
|
||||||
has_abilities: Option<&'a Loc<ast::HasAbilities<'a>>>,
|
has_abilities: Option<&'a Loc<ast::HasAbilities<'a>>>,
|
||||||
) -> Result<Alias, ()> {
|
) -> Result<CanonicalizedOpaque<'a>, ()> {
|
||||||
let alias = canonicalize_alias(
|
let alias = canonicalize_alias(
|
||||||
env,
|
env,
|
||||||
output,
|
output,
|
||||||
|
@ -667,6 +769,7 @@ fn canonicalize_opaque<'a>(
|
||||||
AliasKind::Opaque,
|
AliasKind::Opaque,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let mut derived_defs = Vec::new();
|
||||||
if let Some(has_abilities) = has_abilities {
|
if let Some(has_abilities) = has_abilities {
|
||||||
let has_abilities = has_abilities.value.collection();
|
let has_abilities = has_abilities.value.collection();
|
||||||
|
|
||||||
|
@ -808,7 +911,19 @@ fn canonicalize_opaque<'a>(
|
||||||
.abilities_store
|
.abilities_store
|
||||||
.register_declared_implementations(name.value, impls);
|
.register_declared_implementations(name.value, impls);
|
||||||
} else if let Some((_, members)) = ability.derivable_ability() {
|
} else if let Some((_, members)) = ability.derivable_ability() {
|
||||||
let impls = members.iter().map(|member| (*member, MemberImpl::Derived));
|
let num_members = members.len();
|
||||||
|
|
||||||
|
derived_defs.reserve(num_members);
|
||||||
|
|
||||||
|
let mut impls = Vec::with_capacity(num_members);
|
||||||
|
for &member in members.iter() {
|
||||||
|
let (derived_impl, derived_def) =
|
||||||
|
synthesize_derived_member_impl(env, scope, name.value, name_str, member);
|
||||||
|
|
||||||
|
impls.push((member, MemberImpl::Impl(derived_impl)));
|
||||||
|
derived_defs.push(derived_def);
|
||||||
|
}
|
||||||
|
|
||||||
scope
|
scope
|
||||||
.abilities_store
|
.abilities_store
|
||||||
.register_declared_implementations(name.value, impls);
|
.register_declared_implementations(name.value, impls);
|
||||||
|
@ -854,7 +969,10 @@ fn canonicalize_opaque<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(alias)
|
Ok(CanonicalizedOpaque {
|
||||||
|
opaque_def: alias,
|
||||||
|
derived_defs,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
|
@ -929,7 +1047,11 @@ pub(crate) fn canonicalize_defs<'a>(
|
||||||
scope.register_debug_idents();
|
scope.register_debug_idents();
|
||||||
}
|
}
|
||||||
|
|
||||||
let (aliases, symbols_introduced) = canonicalize_type_defs(
|
let CanonicalizedTypeDefs {
|
||||||
|
aliases,
|
||||||
|
symbols_introduced,
|
||||||
|
derived_defs,
|
||||||
|
} = canonicalize_type_defs(
|
||||||
env,
|
env,
|
||||||
&mut output,
|
&mut output,
|
||||||
var_store,
|
var_store,
|
||||||
|
@ -938,6 +1060,11 @@ pub(crate) fn canonicalize_defs<'a>(
|
||||||
pending_type_defs,
|
pending_type_defs,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add the derived ASTs, so that we create proper canonicalized defs for them.
|
||||||
|
// They can go at the end, and derived defs should never reference anything other than builtin
|
||||||
|
// ability members.
|
||||||
|
pending_value_defs.extend(derived_defs);
|
||||||
|
|
||||||
// Now that we have the scope completely assembled, and shadowing resolved,
|
// Now that we have the scope completely assembled, and shadowing resolved,
|
||||||
// we're ready to canonicalize any body exprs.
|
// we're ready to canonicalize any body exprs.
|
||||||
canonicalize_value_defs(
|
canonicalize_value_defs(
|
||||||
|
@ -1086,6 +1213,12 @@ fn canonicalize_value_defs<'a>(
|
||||||
(can_defs, output, symbols_introduced)
|
(can_defs, output, symbols_introduced)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CanonicalizedTypeDefs<'a> {
|
||||||
|
aliases: VecMap<Symbol, Alias>,
|
||||||
|
symbols_introduced: MutMap<Symbol, Region>,
|
||||||
|
derived_defs: Vec<DerivedDef<'a>>,
|
||||||
|
}
|
||||||
|
|
||||||
fn canonicalize_type_defs<'a>(
|
fn canonicalize_type_defs<'a>(
|
||||||
env: &mut Env<'a>,
|
env: &mut Env<'a>,
|
||||||
output: &mut Output,
|
output: &mut Output,
|
||||||
|
@ -1093,7 +1226,7 @@ fn canonicalize_type_defs<'a>(
|
||||||
scope: &mut Scope,
|
scope: &mut Scope,
|
||||||
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
pending_abilities_in_scope: &PendingAbilitiesInScope,
|
||||||
pending_type_defs: Vec<PendingTypeDef<'a>>,
|
pending_type_defs: Vec<PendingTypeDef<'a>>,
|
||||||
) -> (VecMap<Symbol, Alias>, MutMap<Symbol, Region>) {
|
) -> CanonicalizedTypeDefs<'a> {
|
||||||
enum TypeDef<'a> {
|
enum TypeDef<'a> {
|
||||||
Alias(
|
Alias(
|
||||||
Loc<Symbol>,
|
Loc<Symbol>,
|
||||||
|
@ -1101,6 +1234,7 @@ fn canonicalize_type_defs<'a>(
|
||||||
&'a Loc<ast::TypeAnnotation<'a>>,
|
&'a Loc<ast::TypeAnnotation<'a>>,
|
||||||
),
|
),
|
||||||
Opaque(
|
Opaque(
|
||||||
|
&'a str,
|
||||||
Loc<Symbol>,
|
Loc<Symbol>,
|
||||||
Vec<Loc<Lowercase>>,
|
Vec<Loc<Lowercase>>,
|
||||||
&'a Loc<ast::TypeAnnotation<'a>>,
|
&'a Loc<ast::TypeAnnotation<'a>>,
|
||||||
|
@ -1129,6 +1263,7 @@ fn canonicalize_type_defs<'a>(
|
||||||
type_defs.insert(name.value, TypeDef::Alias(name, vars, ann));
|
type_defs.insert(name.value, TypeDef::Alias(name, vars, ann));
|
||||||
}
|
}
|
||||||
PendingTypeDef::Opaque {
|
PendingTypeDef::Opaque {
|
||||||
|
name_str,
|
||||||
name,
|
name,
|
||||||
vars,
|
vars,
|
||||||
ann,
|
ann,
|
||||||
|
@ -1141,7 +1276,10 @@ fn canonicalize_type_defs<'a>(
|
||||||
// builtin abilities, and hence do not affect the type def sorting. We'll insert
|
// builtin abilities, and hence do not affect the type def sorting. We'll insert
|
||||||
// references of usages when canonicalizing the derives.
|
// references of usages when canonicalizing the derives.
|
||||||
|
|
||||||
type_defs.insert(name.value, TypeDef::Opaque(name, vars, ann, derived));
|
type_defs.insert(
|
||||||
|
name.value,
|
||||||
|
TypeDef::Opaque(name_str, name, vars, ann, derived),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
PendingTypeDef::Ability { name, members } => {
|
PendingTypeDef::Ability { name, members } => {
|
||||||
let mut referenced_symbols = Vec::with_capacity(2);
|
let mut referenced_symbols = Vec::with_capacity(2);
|
||||||
|
@ -1167,6 +1305,7 @@ fn canonicalize_type_defs<'a>(
|
||||||
let sorted = sort_type_defs_before_introduction(referenced_type_symbols);
|
let sorted = sort_type_defs_before_introduction(referenced_type_symbols);
|
||||||
let mut aliases = VecMap::default();
|
let mut aliases = VecMap::default();
|
||||||
let mut abilities = MutMap::default();
|
let mut abilities = MutMap::default();
|
||||||
|
let mut all_derived_defs = Vec::new();
|
||||||
|
|
||||||
for type_name in sorted {
|
for type_name in sorted {
|
||||||
match type_defs.remove(&type_name).unwrap() {
|
match type_defs.remove(&type_name).unwrap() {
|
||||||
|
@ -1188,7 +1327,7 @@ fn canonicalize_type_defs<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TypeDef::Opaque(name, vars, ann, derived) => {
|
TypeDef::Opaque(name_str, name, vars, ann, derived) => {
|
||||||
let alias_and_derives = canonicalize_opaque(
|
let alias_and_derives = canonicalize_opaque(
|
||||||
env,
|
env,
|
||||||
output,
|
output,
|
||||||
|
@ -1196,13 +1335,19 @@ fn canonicalize_type_defs<'a>(
|
||||||
scope,
|
scope,
|
||||||
pending_abilities_in_scope,
|
pending_abilities_in_scope,
|
||||||
name,
|
name,
|
||||||
|
name_str,
|
||||||
ann,
|
ann,
|
||||||
&vars,
|
&vars,
|
||||||
derived,
|
derived,
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Ok(alias) = alias_and_derives {
|
if let Ok(CanonicalizedOpaque {
|
||||||
aliases.insert(name.value, alias);
|
opaque_def,
|
||||||
|
derived_defs,
|
||||||
|
}) = alias_and_derives
|
||||||
|
{
|
||||||
|
aliases.insert(name.value, opaque_def);
|
||||||
|
all_derived_defs.extend(derived_defs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1238,7 +1383,11 @@ fn canonicalize_type_defs<'a>(
|
||||||
pending_abilities_in_scope,
|
pending_abilities_in_scope,
|
||||||
);
|
);
|
||||||
|
|
||||||
(aliases, symbols_introduced)
|
CanonicalizedTypeDefs {
|
||||||
|
aliases,
|
||||||
|
symbols_introduced,
|
||||||
|
derived_defs: all_derived_defs,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resolve all pending abilities, to add them to scope.
|
/// Resolve all pending abilities, to add them to scope.
|
||||||
|
@ -2055,7 +2204,7 @@ fn canonicalize_pending_value_def<'a>(
|
||||||
Some(Loc::at(loc_ann.region, type_annotation)),
|
Some(Loc::at(loc_ann.region, type_annotation)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Body(_loc_pattern, loc_can_pattern, loc_expr) => {
|
Body(loc_can_pattern, loc_expr) => {
|
||||||
//
|
//
|
||||||
canonicalize_pending_body(
|
canonicalize_pending_body(
|
||||||
env,
|
env,
|
||||||
|
@ -2328,6 +2477,7 @@ fn to_pending_alias_or_opaque<'a>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let name_str = name.value;
|
||||||
let name = Loc {
|
let name = Loc {
|
||||||
region: name.region,
|
region: name.region,
|
||||||
value: symbol,
|
value: symbol,
|
||||||
|
@ -2340,6 +2490,7 @@ fn to_pending_alias_or_opaque<'a>(
|
||||||
ann,
|
ann,
|
||||||
},
|
},
|
||||||
AliasKind::Opaque => PendingTypeDef::Opaque {
|
AliasKind::Opaque => PendingTypeDef::Opaque {
|
||||||
|
name_str,
|
||||||
name,
|
name,
|
||||||
vars: can_rigids,
|
vars: can_rigids,
|
||||||
ann,
|
ann,
|
||||||
|
@ -2531,11 +2682,7 @@ fn to_pending_value_def<'a>(
|
||||||
loc_pattern.region,
|
loc_pattern.region,
|
||||||
);
|
);
|
||||||
|
|
||||||
PendingValue::Def(PendingValueDef::Body(
|
PendingValue::Def(PendingValueDef::Body(loc_can_pattern, loc_expr))
|
||||||
loc_pattern,
|
|
||||||
loc_can_pattern,
|
|
||||||
loc_expr,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AnnotatedBody {
|
AnnotatedBody {
|
||||||
|
|
|
@ -8044,4 +8044,21 @@ mod solve_expr {
|
||||||
"{} -> {}",
|
"{} -> {}",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn derive_hash_for_opaque() {
|
||||||
|
infer_queries!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app "test" provides [main] to "./platform"
|
||||||
|
|
||||||
|
N := U8 has [Hash]
|
||||||
|
|
||||||
|
main = \hasher, @N n -> Hash.hash hasher (@N n)
|
||||||
|
# ^^^^^^^^^
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
@"N#Hash.hash(3) : a, N -[[#N_hash(3)]]-> a | a has Hasher"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1590,6 +1590,33 @@ mod hash {
|
||||||
RocList<u8>
|
RocList<u8>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn derived_hash_for_opaque_record() {
|
||||||
|
assert_evals_to!(
|
||||||
|
&format!(
|
||||||
|
indoc!(
|
||||||
|
r#"
|
||||||
|
app "test" provides [main] to "./platform"
|
||||||
|
|
||||||
|
{}
|
||||||
|
|
||||||
|
Q := {{ a: U8, b: U8, c: U8 }} has [Hash]
|
||||||
|
|
||||||
|
q = @Q {{ a: 15, b: 27, c: 31 }}
|
||||||
|
|
||||||
|
main =
|
||||||
|
@THasher []
|
||||||
|
|> Hash.hash q
|
||||||
|
|> tRead
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
TEST_HASHER,
|
||||||
|
),
|
||||||
|
RocList::from_slice(&[15, 27, 31]),
|
||||||
|
RocList<u8>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue