mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-29 14:54:47 +00:00
Report incomplete ability implementations
This commit is contained in:
parent
b86bf94d92
commit
127d435ff2
4 changed files with 170 additions and 13 deletions
|
@ -1,4 +1,4 @@
|
|||
use roc_collections::all::{MutMap, MutSet};
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::Region;
|
||||
use roc_types::{subs::Variable, types::Type};
|
||||
|
@ -13,6 +13,13 @@ pub struct AbilityMemberData {
|
|||
pub region: Region,
|
||||
}
|
||||
|
||||
/// A particular specialization of an ability member.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct MemberSpecialization {
|
||||
pub symbol: Symbol,
|
||||
pub region: Region,
|
||||
}
|
||||
|
||||
/// Stores information about what abilities exist in a scope, what it means to implement an
|
||||
/// ability, and what types implement them.
|
||||
// TODO(abilities): this should probably go on the Scope, I don't put it there for now because we
|
||||
|
@ -35,9 +42,9 @@ pub struct AbilitiesStore {
|
|||
/// We keep the mapping #hash1->#hash
|
||||
specialization_to_root: MutMap<Symbol, Symbol>,
|
||||
|
||||
/// Tuples of (type, member) specifying that `type` declares an implementation of an ability
|
||||
/// member `member`.
|
||||
declared_specializations: MutSet<(Symbol, Symbol)>,
|
||||
/// Maps a tuple (member, type) specifying that `type` declares an implementation of an ability
|
||||
/// member `member`, to the exact symbol that implements the ability.
|
||||
declared_specializations: MutMap<(Symbol, Symbol), MemberSpecialization>,
|
||||
}
|
||||
|
||||
impl AbilitiesStore {
|
||||
|
@ -69,15 +76,18 @@ impl AbilitiesStore {
|
|||
}
|
||||
|
||||
/// Records a specialization of `ability_member` with specialized type `implementing_type`.
|
||||
/// Entries via this function are considered a source of truth. It must be ensured that a
|
||||
/// specialization is validated before being registered here.
|
||||
pub fn register_specialization_for_type(
|
||||
&mut self,
|
||||
implementing_type: Symbol,
|
||||
ability_member: Symbol,
|
||||
implementing_type: Symbol,
|
||||
specialization: MemberSpecialization,
|
||||
) {
|
||||
let is_new_insert = self
|
||||
let old_spec = self
|
||||
.declared_specializations
|
||||
.insert((implementing_type, ability_member));
|
||||
debug_assert!(is_new_insert, "Replacing existing implementation");
|
||||
.insert((ability_member, implementing_type), specialization);
|
||||
debug_assert!(old_spec.is_none(), "Replacing existing specialization");
|
||||
}
|
||||
|
||||
/// Checks if `name` is a root ability member symbol name.
|
||||
|
@ -123,9 +133,27 @@ impl AbilitiesStore {
|
|||
Some((*root_symbol, root_data))
|
||||
}
|
||||
|
||||
/// Finds the ability member definition for a member name.
|
||||
pub fn member_def(&self, member: Symbol) -> Option<&AbilityMemberData> {
|
||||
self.ability_members.get(&member)
|
||||
}
|
||||
|
||||
/// Returns an iterator over pairs (ability member, type) specifying that
|
||||
/// "ability member" has a specialization with type "type".
|
||||
pub fn get_known_specializations<'lifetime_for_copied>(
|
||||
&'lifetime_for_copied self,
|
||||
) -> impl Iterator<Item = (Symbol, Symbol)> + 'lifetime_for_copied {
|
||||
self.declared_specializations.keys().copied()
|
||||
}
|
||||
|
||||
/// Retrieves the specialization of `member` for `typ`, if it exists.
|
||||
pub fn get_specialization(&self, member: Symbol, typ: Symbol) -> Option<MemberSpecialization> {
|
||||
self.declared_specializations.get(&(member, typ)).copied()
|
||||
}
|
||||
|
||||
/// Returns pairs of (type, ability member) specifying that "ability member" has a
|
||||
/// specialization with type "type".
|
||||
pub fn get_known_specializations(&self) -> &MutSet<(Symbol, Symbol)> {
|
||||
&self.declared_specializations
|
||||
pub fn members_of_ability(&self, ability: Symbol) -> Option<&[Symbol]> {
|
||||
self.members_of_ability.get(&ability).map(|v| v.as_ref())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use bumpalo::Bump;
|
||||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::abilities::{AbilitiesStore, MemberSpecialization};
|
||||
use roc_can::constraint::Constraint::{self, *};
|
||||
use roc_can::constraint::{Constraints, LetConstraint};
|
||||
use roc_can::expected::{Expected, PExpected};
|
||||
|
@ -76,6 +76,13 @@ pub enum TypeError {
|
|||
CircularType(Region, Symbol, ErrorType),
|
||||
BadType(roc_types::types::Problem),
|
||||
UnexposedLookup(Symbol),
|
||||
IncompleteAbilityImplementation {
|
||||
// TODO(abilities): have general types here, not just opaques
|
||||
typ: Symbol,
|
||||
ability: Symbol,
|
||||
specialized_members: Vec<Loc<Symbol>>,
|
||||
missing_members: Vec<Loc<Symbol>>,
|
||||
},
|
||||
}
|
||||
|
||||
use roc_types::types::Alias;
|
||||
|
@ -568,7 +575,34 @@ pub fn run_in_place(
|
|||
&mut deferred_must_implement_abilities,
|
||||
);
|
||||
|
||||
// TODO run through and check that all abilities are properly implemented
|
||||
// Now that the module has been solved, we can run through and check all
|
||||
// types claimed to implement abilities.
|
||||
deferred_must_implement_abilities.dedup();
|
||||
for MustImplementAbility { typ, ability } in deferred_must_implement_abilities.into_iter() {
|
||||
let members_of_ability = abilities_store.members_of_ability(ability).unwrap();
|
||||
let mut specialized_members = Vec::with_capacity(members_of_ability.len());
|
||||
let mut missing_members = Vec::with_capacity(members_of_ability.len());
|
||||
for &member in members_of_ability {
|
||||
match abilities_store.get_specialization(member, typ) {
|
||||
None => {
|
||||
let root_data = abilities_store.member_def(member).unwrap();
|
||||
missing_members.push(Loc::at(root_data.region, member));
|
||||
}
|
||||
Some(specialization) => {
|
||||
specialized_members.push(Loc::at(specialization.region, member));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !missing_members.is_empty() {
|
||||
problems.push(TypeError::IncompleteAbilityImplementation {
|
||||
typ,
|
||||
ability,
|
||||
specialized_members,
|
||||
missing_members,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
state.env
|
||||
}
|
||||
|
@ -1254,8 +1288,17 @@ fn check_ability_specialization(
|
|||
"If there's more than one, the definition is ambiguous - this should be an error"
|
||||
);
|
||||
|
||||
// This is a valid specialization! Record it.
|
||||
let specialization_type = ability_implementations_for_specialization[0].typ;
|
||||
abilities_store.register_specialization_for_type(specialization_type, root_symbol);
|
||||
let specialization = MemberSpecialization {
|
||||
symbol,
|
||||
region: symbol_loc_var.region,
|
||||
};
|
||||
abilities_store.register_specialization_for_type(
|
||||
root_symbol,
|
||||
specialization_type,
|
||||
specialization,
|
||||
);
|
||||
|
||||
// Store the checks for what abilities must be implemented to be checked after the
|
||||
// whole module is complete.
|
||||
|
|
|
@ -118,6 +118,53 @@ pub fn type_problem<'b>(
|
|||
other => panic!("unhandled bad type: {:?}", other),
|
||||
}
|
||||
}
|
||||
IncompleteAbilityImplementation {
|
||||
typ,
|
||||
ability,
|
||||
specialized_members,
|
||||
missing_members,
|
||||
} => {
|
||||
let title = "INCOMPLETE ABILITY IMPLEMENTATION".to_string();
|
||||
|
||||
let mut stack = vec![alloc.concat(vec![
|
||||
alloc.reflow("The type "),
|
||||
alloc.symbol_unqualified(typ),
|
||||
alloc.reflow(" does not fully implement the ability "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
alloc.reflow(". The following specializations are missing:"),
|
||||
])];
|
||||
|
||||
for member in missing_members.into_iter() {
|
||||
stack.push(alloc.concat(vec![
|
||||
alloc.reflow("A specialization for "),
|
||||
alloc.symbol_unqualified(member.value),
|
||||
alloc.reflow(", which is defined here:"),
|
||||
]));
|
||||
stack.push(alloc.region(lines.convert_region(member.region)));
|
||||
}
|
||||
|
||||
debug_assert!(!specialized_members.is_empty());
|
||||
|
||||
stack.push(alloc.concat(vec![
|
||||
alloc.note(""),
|
||||
alloc.symbol_unqualified(typ),
|
||||
alloc.reflow(" specializes the following members of "),
|
||||
alloc.symbol_unqualified(ability),
|
||||
alloc.reflow(":"),
|
||||
]));
|
||||
|
||||
for spec in specialized_members {
|
||||
stack.push(alloc.concat(vec![
|
||||
alloc.symbol_unqualified(spec.value),
|
||||
alloc.reflow(", specialized here:"),
|
||||
]));
|
||||
stack.push(alloc.region(lines.convert_region(spec.region)));
|
||||
}
|
||||
|
||||
let doc = alloc.stack(stack);
|
||||
|
||||
report(title, doc, filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9462,4 +9462,43 @@ I need all branches in an `if` to have the same type!
|
|||
),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ability_specialization_is_incomplete() {
|
||||
new_report_problem_as(
|
||||
indoc!(
|
||||
r#"
|
||||
app "test" provides [ eq, le ] to "./platform"
|
||||
|
||||
Eq has
|
||||
eq : a, a -> Bool | a has Eq
|
||||
le : a, a -> Bool | a has Eq
|
||||
|
||||
Id := U64
|
||||
|
||||
eq = \$Id m, $Id n -> m == n
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
── INCOMPLETE ABILITY IMPLEMENTATION ───────────────────────────────────────────
|
||||
|
||||
The type `Id` does not fully implement the ability `Eq`. The following
|
||||
specializations are missing:
|
||||
|
||||
A specialization for `le`, which is defined here:
|
||||
|
||||
5│ le : a, a -> Bool | a has Eq
|
||||
^^
|
||||
|
||||
Note: `Id` specializes the following members of `Eq`:
|
||||
|
||||
`eq`, specialized here:
|
||||
|
||||
9│ eq = \$Id m, $Id n -> m == n
|
||||
^^
|
||||
"#
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue