diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 0dd250fef9..3fd2438f9f 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -578,12 +578,14 @@ fn resolve_abilities<'a>( // What variables in the annotation are bound to the parent ability, and what variables // are bound to some other ability? - let (variables_bound_to_ability, variables_bound_to_other_abilities): (Vec<_>, Vec<_>) = - member_annot - .introduced_variables - .able - .iter() - .partition(|av| av.ability == loc_ability_name.value); + let (variables_bound_to_ability, _variables_bound_to_other_abilities): ( + Vec<_>, + Vec<_>, + ) = member_annot + .introduced_variables + .able + .iter() + .partition(|av| av.ability == loc_ability_name.value); let mut bad_has_clauses = false; @@ -618,18 +620,6 @@ fn resolve_abilities<'a>( bad_has_clauses = true; } - if !variables_bound_to_other_abilities.is_empty() { - // Disallow variables bound to other abilities, for now. - for bad_variable in variables_bound_to_other_abilities.iter() { - env.problem(Problem::AbilityMemberBindsExternalAbility { - member: member_sym, - ability: loc_ability_name.value, - region: bad_variable.first_seen, - }); - } - bad_has_clauses = true; - } - if bad_has_clauses { // Pretend the member isn't a part of the ability continue; diff --git a/compiler/can/src/traverse.rs b/compiler/can/src/traverse.rs index 55ecdf14b9..8f579d39a7 100644 --- a/compiler/can/src/traverse.rs +++ b/compiler/can/src/traverse.rs @@ -45,11 +45,12 @@ fn walk_def(visitor: &mut V, def: &Def) { .. } = def; - visitor.visit_pattern( - &loc_pattern.value, - loc_pattern.region, - loc_pattern.value.opt_var(), - ); + let opt_var = match loc_pattern.value { + Pattern::Identifier(..) | Pattern::AbilityMemberSpecialization { .. } => Some(*expr_var), + _ => loc_pattern.value.opt_var(), + }; + + visitor.visit_pattern(&loc_pattern.value, loc_pattern.region, opt_var); visitor.visit_expr(&loc_expr.value, loc_expr.region, *expr_var); if let Some(annot) = &annotation { visitor.visit_annotation(annot); @@ -70,6 +71,9 @@ fn walk_expr(visitor: &mut V, expr: &Expr) { } => { walk_when(visitor, *cond_var, *expr_var, loc_cond, branches); } + Expr::Call(f, loc_args, _) => { + walk_call(visitor, f, loc_args); + } e => todo!("{:?}", e), } } @@ -120,6 +124,18 @@ fn walk_when_branch(visitor: &mut V, branch: &WhenBranch, expr_var: } } +fn walk_call( + visitor: &mut V, + f: &(Variable, Loc, Variable, Variable), + loc_args: &[(Variable, Loc)], +) { + let (fn_var, loc_fn_expr, _lambda_set, _ret) = f; + visitor.visit_expr(&loc_fn_expr.value, loc_fn_expr.region, *fn_var); + loc_args + .iter() + .for_each(|(v, e)| visitor.visit_expr(&e.value, e.region, *v)); +} + fn walk_pattern(_visitor: &mut V, _pat: &Pattern) { todo!() } diff --git a/compiler/mono/src/ir.rs b/compiler/mono/src/ir.rs index 75e3cf04de..8abbaf9b14 100644 --- a/compiler/mono/src/ir.rs +++ b/compiler/mono/src/ir.rs @@ -4922,6 +4922,14 @@ fn get_specialization<'a>( Some(member) => { let snapshot = env.subs.snapshot(); instantiate_rigids(env.subs, member.signature_var); + let this_f = env.subs.get_content_without_compacting(symbol_var); + let member_f = env + .subs + .get_content_without_compacting(member.signature_var); + use roc_types::subs::SubsFmtContent; + let this_f = SubsFmtContent(&this_f, env.subs); + let member_f = SubsFmtContent(&member_f, env.subs); + dbg!(symbol, this_f, member_f); let (_, must_implement_ability) = unify( env.subs, symbol_var, diff --git a/compiler/solve/src/ability.rs b/compiler/solve/src/ability.rs index a9796590cf..d7479c0780 100644 --- a/compiler/solve/src/ability.rs +++ b/compiler/solve/src/ability.rs @@ -173,9 +173,12 @@ pub fn type_implementing_member( let ability_implementations_for_specialization = specialization_must_implement_constraints .clone() - .get_unique(); + .get_unique() + .into_iter() + .filter(|mia| mia.ability == ability) + .count(); - ability_implementations_for_specialization.len() + ability_implementations_for_specialization }, 1, "Multiple variables bound to an ability - this is ambiguous and should have been caught in canonicalization: {:?}", diff --git a/compiler/solve/tests/solve_expr.rs b/compiler/solve/tests/solve_expr.rs index a161271d74..c5a5d96821 100644 --- a/compiler/solve/tests/solve_expr.rs +++ b/compiler/solve/tests/solve_expr.rs @@ -25,7 +25,8 @@ mod solve_expr { // HELPERS lazy_static! { - static ref RE_TYPE_QUERY: Regex = Regex::new(r#"^\s*#\s*(?P\^+)\s*$"#).unwrap(); + static ref RE_TYPE_QUERY: Regex = + Regex::new(r#"(?P\^+)(?:\{-(?P\d+)\})?"#).unwrap(); } #[derive(Debug, Clone, Copy)] @@ -35,9 +36,14 @@ mod solve_expr { let line_info = LineInfo::new(src); let mut queries = vec![]; for (i, line) in src.lines().enumerate() { - if let Some(capture) = RE_TYPE_QUERY.captures(line) { + for capture in RE_TYPE_QUERY.captures_iter(line) { let wher = capture.name("where").unwrap(); + let subtract_col = capture + .name("sub") + .and_then(|m| str::parse(m.as_str()).ok()) + .unwrap_or(0); let (start, end) = (wher.start() as u32, wher.end() as u32); + let (start, end) = (start - subtract_col, end - subtract_col); let last_line = i as u32 - 1; let start_lc = LineColumn { line: last_line, @@ -273,7 +279,8 @@ mod solve_expr { let start = region.start().offset; let end = region.end().offset; let text = &src[start as usize..end as usize]; - let var = find_type_at(region, &decls).expect(&format!("No type for {}!", &text)); + let var = find_type_at(region, &decls) + .expect(&format!("No type for {} ({:?})!", &text, region)); name_all_type_vars(var, subs); let content = subs.get_content_without_compacting(var); @@ -6232,6 +6239,38 @@ mod solve_expr { "# ), "F b -> b", + ); + } + + #[test] + fn ability_member_takes_different_able_variable() { + infer_queries( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has hash : a -> U64 | a has Hash + + IntoHash has intoHash : a, b -> b | a has IntoHash, b has Hash + + Id := U64 + hash = \$Id n -> n + #^^^^{-1} + + User := Id + intoHash = \$User id, _ -> id + #^^^^^^^^{-1} + + result = hash (intoHash ($User ($Id 123)) ($Id 1)) + # ^^^^ ^^^^^^^^ + "# + ), + &[ + "hash : Id -> U64", + "intoHash : User, Id -> Id", + "hash : Id -> U64", + "intoHash : User, Id -> Id", + ], ) } diff --git a/compiler/test_gen/src/gen_abilities.rs b/compiler/test_gen/src/gen_abilities.rs index 4da1464de1..66dd302d1b 100644 --- a/compiler/test_gen/src/gen_abilities.rs +++ b/compiler/test_gen/src/gen_abilities.rs @@ -216,3 +216,29 @@ fn ability_used_as_type_still_compiles() { u64 ) } + +#[test] +#[cfg(any(feature = "gen-llvm", feature = "gen-wasm"))] +fn ability_member_takes_different_able_variable() { + assert_evals_to!( + indoc!( + r#" + app "test" provides [ result ] to "./platform" + + Hash has hash : a -> U64 | a has Hash + + IntoHash has intoHash : a, b -> b | a has IntoHash, b has Hash + + Id := U64 + hash = \$Id n -> n + + User := Id + intoHash = \$User id, _ -> id + + result = hash (intoHash ($User ($Id 123)) ($Id 1)) + "# + ), + 123, + u64 + ) +}