diff --git a/crates/compiler/can/src/def.rs b/crates/compiler/can/src/def.rs index 0ff6a98b4a..78a0f0dd98 100644 --- a/crates/compiler/can/src/def.rs +++ b/crates/compiler/can/src/def.rs @@ -436,7 +436,7 @@ fn canonicalize_claimed_ability_impl<'a>( let label_str = label.value; let region = label.region; - let _member_symbol = + let member_symbol = match env.qualified_lookup_with_module_id(scope, ability_home, label_str, region) { Ok(symbol) => symbol, Err(_) => { @@ -449,21 +449,18 @@ fn canonicalize_claimed_ability_impl<'a>( } }; - todo!(); - // match scope.lookup(&label_str.into(), label.region) { - // Ok(symbol) => { - // return Ok((member_symbol, symbol)); - // } - // Err(_) => { - // // [Eq { eq }] - - // env.problem(Problem::ImplementationNotFound { - // ability, - // member: scope.lookup(), - // region: label.region, - // }); - // } - // } + match scope.lookup_ability_member_shadow(member_symbol) { + Some(symbol) => { + return Ok((member_symbol, symbol)); + } + None => { + env.problem(Problem::ImplementationNotFound { + member: member_symbol, + region: label.region, + }); + Err(()) + } + } } AssignedField::RequiredValue(label, _spaces, value) => { let impl_ident = match value.value { diff --git a/crates/compiler/can/src/scope.rs b/crates/compiler/can/src/scope.rs index 50751d912f..3cdb94595d 100644 --- a/crates/compiler/can/src/scope.rs +++ b/crates/compiler/can/src/scope.rs @@ -30,6 +30,12 @@ pub struct Scope { /// Identifiers that are imported (and introduced in the header) imports: Vec<(Ident, Symbol, Region)>, + /// Shadows of an ability member, for example a local specialization of `eq` for the ability + /// member `Eq has eq : a, a -> Bool` gets a shadow symbol it can use for its implementation. + /// + /// Only one shadow of an ability member is permitted per scope. + shadows: VecMap>, + /// Identifiers that are in scope, and defined in the current module pub locals: ScopedIdentIds, } @@ -51,6 +57,7 @@ impl Scope { locals: ScopedIdentIds::from_ident_ids(home, initial_ident_ids), aliases: VecMap::default(), abilities_store: starting_abilities_store, + shadows: VecMap::default(), imports, } } @@ -59,6 +66,10 @@ impl Scope { self.lookup_str(ident.as_str(), region) } + pub fn lookup_ability_member_shadow(&self, member: Symbol) -> Option { + self.shadows.get(&member).map(|loc_shadow| loc_shadow.value) + } + pub fn add_docs_imports(&mut self) { self.imports .push(("Dict".into(), Symbol::DICT_DICT, Region::zero())); @@ -306,11 +317,26 @@ impl Scope { .iter() .any(|(_, members)| members.iter().any(|m| *m == original_symbol)) { - // TODO: remove register_specializing_symbol - self.abilities_store - .register_specializing_symbol(shadow_symbol, original_symbol); + match self.shadows.get(&original_symbol) { + Some(loc_original_shadow) => { + // Duplicate shadow of an ability members; that's illegal. + let shadow = Loc { + value: ident.clone(), + region, + }; + Err((loc_original_shadow.region, shadow, shadow_symbol)) + } + None => { + // TODO: remove register_specializing_symbol + self.abilities_store + .register_specializing_symbol(shadow_symbol, original_symbol); - Ok((shadow_symbol, Some(original_symbol))) + self.shadows + .insert(original_symbol, Loc::at(region, shadow_symbol)); + + Ok((shadow_symbol, Some(original_symbol))) + } + } } else { // This is an illegal shadow. let shadow = Loc { diff --git a/crates/compiler/problem/src/can.rs b/crates/compiler/problem/src/can.rs index 4df0296f35..f9751b597e 100644 --- a/crates/compiler/problem/src/can.rs +++ b/crates/compiler/problem/src/can.rs @@ -131,6 +131,10 @@ pub enum Problem { AbilityUsedAsType(Lowercase, Symbol, Region), NestedSpecialization(Symbol, Region), IllegalClaimedAbility(Region), + ImplementationNotFound { + member: Symbol, + region: Region, + }, NotAnAbilityMember { ability: Symbol, name: String, diff --git a/crates/reporting/src/error/canonicalize.rs b/crates/reporting/src/error/canonicalize.rs index b78276baf7..f6cc665adb 100644 --- a/crates/reporting/src/error/canonicalize.rs +++ b/crates/reporting/src/error/canonicalize.rs @@ -48,6 +48,7 @@ const ABILITY_NOT_ON_TOPLEVEL: &str = "ABILITY NOT ON TOP-LEVEL"; const SPECIALIZATION_NOT_ON_TOPLEVEL: &str = "SPECIALIZATION NOT ON TOP-LEVEL"; const ABILITY_USED_AS_TYPE: &str = "ABILITY USED AS TYPE"; const ILLEGAL_DERIVE: &str = "ILLEGAL DERIVE"; +const IMPLEMENTATION_NOT_FOUND: &str = "IMPLEMENTATION NOT FOUND"; const NOT_AN_ABILITY_MEMBER: &str = "NOT AN ABILITY MEMBER"; const OPTIONAL_ABILITY_IMPLEMENTATION: &str = "OPTIONAL ABILITY IMPLEMENTATION"; const QUALIFIED_ABILITY_IMPLEMENTATION: &str = "QUALIFIED ABILITY IMPLEMENTATION"; @@ -769,6 +770,18 @@ pub fn can_problem<'b>( title = NOT_AN_ABILITY_MEMBER.to_string(); severity = Severity::RuntimeError; } + Problem::ImplementationNotFound { member, region } => { + let member_str = member.as_str(alloc.interns); + doc = alloc.stack([ + alloc.concat([ + alloc.reflow("An implementation of "), alloc.symbol_unqualified(member), alloc.reflow(" could not be found in this scope:"), + ]), + alloc.region(lines.convert_region(region)), + alloc.tip().append(alloc.concat([alloc.reflow("consider adding a value of name "), alloc.symbol_unqualified(member), alloc.reflow(" in this scope, or using another variable that implements this ability member, like "), alloc.type_str(&format!("{{ {}: my{} }}", member_str, member_str))])) + ]); + title = IMPLEMENTATION_NOT_FOUND.to_string(); + severity = Severity::RuntimeError; + } Problem::OptionalAbilityImpl { ability, region } => { let hint = if ability.is_builtin() { alloc.hint("").append( diff --git a/crates/reporting/tests/test_reporting.rs b/crates/reporting/tests/test_reporting.rs index 64e13a18b3..cb111f9fe1 100644 --- a/crates/reporting/tests/test_reporting.rs +++ b/crates/reporting/tests/test_reporting.rs @@ -9134,11 +9134,10 @@ All branches in an `if` must have the same type! ); test_report!( - #[ignore="TODO"] opaque_ability_impl_not_found_shorthand_syntax, indoc!( r#" - app "test" provides [] to "./platform" + app "test" provides [A] to "./platform" Eq has eq : a, a -> U64 | a has Eq @@ -9146,6 +9145,16 @@ All branches in an `if` must have the same type! "# ), @r###" + ── IMPLEMENTATION NOT FOUND ────────────────────────────── /code/proj/Main.roc ─ + + An implementation of `eq` could not be found in this scope: + + 5│ A := U8 has [Eq {eq}] + ^^ + + Tip: consider adding a value of name `eq` in this scope, or using + another variable that implements this ability member, like + { eq: myeq } "### );