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_module::symbol::Symbol;
|
||||||
use roc_region::all::Region;
|
use roc_region::all::Region;
|
||||||
use roc_types::{subs::Variable, types::Type};
|
use roc_types::{subs::Variable, types::Type};
|
||||||
|
@ -13,6 +13,13 @@ pub struct AbilityMemberData {
|
||||||
pub region: Region,
|
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
|
/// Stores information about what abilities exist in a scope, what it means to implement an
|
||||||
/// ability, and what types implement them.
|
/// 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
|
// 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
|
/// We keep the mapping #hash1->#hash
|
||||||
specialization_to_root: MutMap<Symbol, Symbol>,
|
specialization_to_root: MutMap<Symbol, Symbol>,
|
||||||
|
|
||||||
/// Tuples of (type, member) specifying that `type` declares an implementation of an ability
|
/// Maps a tuple (member, type) specifying that `type` declares an implementation of an ability
|
||||||
/// member `member`.
|
/// member `member`, to the exact symbol that implements the ability.
|
||||||
declared_specializations: MutSet<(Symbol, Symbol)>,
|
declared_specializations: MutMap<(Symbol, Symbol), MemberSpecialization>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AbilitiesStore {
|
impl AbilitiesStore {
|
||||||
|
@ -69,15 +76,18 @@ impl AbilitiesStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Records a specialization of `ability_member` with specialized type `implementing_type`.
|
/// 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(
|
pub fn register_specialization_for_type(
|
||||||
&mut self,
|
&mut self,
|
||||||
implementing_type: Symbol,
|
|
||||||
ability_member: Symbol,
|
ability_member: Symbol,
|
||||||
|
implementing_type: Symbol,
|
||||||
|
specialization: MemberSpecialization,
|
||||||
) {
|
) {
|
||||||
let is_new_insert = self
|
let old_spec = self
|
||||||
.declared_specializations
|
.declared_specializations
|
||||||
.insert((implementing_type, ability_member));
|
.insert((ability_member, implementing_type), specialization);
|
||||||
debug_assert!(is_new_insert, "Replacing existing implementation");
|
debug_assert!(old_spec.is_none(), "Replacing existing specialization");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if `name` is a root ability member symbol name.
|
/// Checks if `name` is a root ability member symbol name.
|
||||||
|
@ -123,9 +133,27 @@ impl AbilitiesStore {
|
||||||
Some((*root_symbol, root_data))
|
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
|
/// Returns pairs of (type, ability member) specifying that "ability member" has a
|
||||||
/// specialization with type "type".
|
/// specialization with type "type".
|
||||||
pub fn get_known_specializations(&self) -> &MutSet<(Symbol, Symbol)> {
|
pub fn members_of_ability(&self, ability: Symbol) -> Option<&[Symbol]> {
|
||||||
&self.declared_specializations
|
self.members_of_ability.get(&ability).map(|v| v.as_ref())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use roc_can::abilities::AbilitiesStore;
|
use roc_can::abilities::{AbilitiesStore, MemberSpecialization};
|
||||||
use roc_can::constraint::Constraint::{self, *};
|
use roc_can::constraint::Constraint::{self, *};
|
||||||
use roc_can::constraint::{Constraints, LetConstraint};
|
use roc_can::constraint::{Constraints, LetConstraint};
|
||||||
use roc_can::expected::{Expected, PExpected};
|
use roc_can::expected::{Expected, PExpected};
|
||||||
|
@ -76,6 +76,13 @@ pub enum TypeError {
|
||||||
CircularType(Region, Symbol, ErrorType),
|
CircularType(Region, Symbol, ErrorType),
|
||||||
BadType(roc_types::types::Problem),
|
BadType(roc_types::types::Problem),
|
||||||
UnexposedLookup(Symbol),
|
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;
|
use roc_types::types::Alias;
|
||||||
|
@ -568,7 +575,34 @@ pub fn run_in_place(
|
||||||
&mut deferred_must_implement_abilities,
|
&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
|
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"
|
"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;
|
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
|
// Store the checks for what abilities must be implemented to be checked after the
|
||||||
// whole module is complete.
|
// whole module is complete.
|
||||||
|
|
|
@ -118,6 +118,53 @@ pub fn type_problem<'b>(
|
||||||
other => panic!("unhandled bad type: {:?}", other),
|
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