mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
Support custom abilities for opaques with immaterial lambda sets
If a specialization of an ability member has a lambda set that is not
reflected in the unspecialized lambda sets of the member's prototype
signature, then the specialization lambda set is deemed to be immaterial
to the specialization lambda set mapping, and we don't need to associate
it with a particular region from the prototype signature.
This can happen when an opaque contains functions that are some specific
than the generalized prototype signature; for example, when we are
defining a custom impl for an opaque with functions.
Addresses a bug found in 8c3158c3e0
This commit is contained in:
parent
ef5d83a42d
commit
e36618b9e9
5 changed files with 108 additions and 5 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4142,6 +4142,7 @@ dependencies = [
|
|||
"roc_error_macros",
|
||||
"roc_module",
|
||||
"roc_solve_problem",
|
||||
"roc_tracing",
|
||||
"roc_types",
|
||||
]
|
||||
|
||||
|
|
|
@ -380,7 +380,6 @@ mod solve_expr {
|
|||
let known_specializations = abilities_store.iter_declared_implementations().filter_map(
|
||||
|(impl_key, member_impl)| match member_impl {
|
||||
MemberImpl::Impl(impl_symbol) => {
|
||||
dbg!(impl_symbol);
|
||||
let specialization = abilities_store.specialization_info(*impl_symbol).expect(
|
||||
"declared implementations should be resolved conclusively after solving",
|
||||
);
|
||||
|
@ -8320,4 +8319,48 @@ mod solve_expr {
|
|||
print_only_under_alias: true
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impl_ability_for_opaque_with_lambda_sets() {
|
||||
infer_queries!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [isEqQ] to "./platform"
|
||||
|
||||
Q := [ F (Str -> Str), G ] has [Eq { isEq: isEqQ }]
|
||||
|
||||
isEqQ = \@Q q1, @Q q2 -> when T q1 q2 is
|
||||
#^^^^^{-1}
|
||||
T (F _) (F _) -> Bool.true
|
||||
T G G -> Bool.true
|
||||
_ -> Bool.false
|
||||
"#
|
||||
),
|
||||
@"isEqQ : Q, Q -[[isEqQ(0)]]-> Bool"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn impl_ability_for_opaque_with_lambda_sets_material() {
|
||||
infer_queries!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Q := ({} -> Str) has [Eq {isEq: isEqQ}]
|
||||
|
||||
isEqQ = \@Q f1, @Q f2 -> (f1 {} == f2 {})
|
||||
#^^^^^{-1}
|
||||
|
||||
main = isEqQ (@Q \{} -> "a") (@Q \{} -> "a")
|
||||
# ^^^^^
|
||||
"#
|
||||
),
|
||||
@r###"
|
||||
isEqQ : ({} -[[]]-> Str), ({} -[[]]-> Str) -[[isEqQ(2)]]-> [False, True]
|
||||
isEqQ : ({} -[[6(6), 7(7)]]-> Str), ({} -[[6(6), 7(7)]]-> Str) -[[isEqQ(2)]]-> [False, True]
|
||||
"###
|
||||
print_only_under_alias: true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1734,6 +1734,45 @@ mod eq {
|
|||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_eq_impl_for_fn_opaque() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Q := ({} -> Str) has [Eq {isEq: isEqQ}]
|
||||
|
||||
isEqQ = \@Q _, @Q _ -> Bool.true
|
||||
|
||||
main = isEqQ (@Q \{} -> "a") (@Q \{} -> "a")
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore = "needs https://github.com/roc-lang/roc/issues/4557 first"]
|
||||
fn custom_eq_impl_for_fn_opaque_material() {
|
||||
assert_evals_to!(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [main] to "./platform"
|
||||
|
||||
Q := ({} -> Str) has [Eq {isEq: isEqQ}]
|
||||
|
||||
isEqQ = \@Q f1, @Q f2 -> (f1 {} == f2 {})
|
||||
|
||||
main = isEqQ (@Q \{} -> "a") (@Q \{} -> "a")
|
||||
"#
|
||||
),
|
||||
true,
|
||||
bool
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn derive_structural_eq() {
|
||||
assert_evals_to!(
|
||||
|
|
|
@ -29,3 +29,6 @@ path = "../can"
|
|||
|
||||
[dependencies.roc_solve_problem]
|
||||
path = "../solve_problem"
|
||||
|
||||
[dependencies.roc_tracing]
|
||||
path = "../../tracing"
|
||||
|
|
|
@ -1259,12 +1259,29 @@ fn extract_specialization_lambda_set<M: MetaCollector>(
|
|||
debug_assert!(member_rec_var.is_none());
|
||||
|
||||
let member_uls = env.subs.get_subs_slice(member_uls_slice);
|
||||
debug_assert_eq!(
|
||||
member_uls.len(),
|
||||
1,
|
||||
"member signature lambda sets should contain only one unspecialized lambda set"
|
||||
debug_assert!(
|
||||
member_uls.len() <= 1,
|
||||
"member signature lambda sets should contain at most one unspecialized lambda set"
|
||||
);
|
||||
|
||||
if member_uls.is_empty() {
|
||||
// This can happen if the specialized type has a lambda set that is determined to be
|
||||
// immaterial in the implementation of the specialization, because the specialization
|
||||
// lambda set does not line up with one required by the ability member prototype.
|
||||
// As an example, consider
|
||||
//
|
||||
// Q := [ F (Str -> Str) ] has [Eq {isEq}]
|
||||
//
|
||||
// isEq = \@Q _, @Q _ -> Bool.false
|
||||
//
|
||||
// here the lambda set of `F`'s payload is part of the specialization signature, but it is
|
||||
// irrelevant to the specialization. As such, I believe it is safe to drop the
|
||||
// empty specialization lambda set.
|
||||
roc_tracing::info!(ambient_function=?env.subs.get_root_key_without_compacting(specialization_lset.ambient_function), "ambient function in a specialization has a zero-lambda set");
|
||||
|
||||
return merge(env, ctx, Content::LambdaSet(specialization_lset));
|
||||
}
|
||||
|
||||
let Uls(_, member, region) = member_uls[0];
|
||||
|
||||
let mut outcome: Outcome<M> = merge(env, ctx, Content::LambdaSet(specialization_lset));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue