mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 16:44:33 +00:00
Check derivability of abilities for structural types
This commit is contained in:
parent
0e63efdf09
commit
2c87214b44
4 changed files with 410 additions and 167 deletions
|
@ -1,15 +1,15 @@
|
|||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_collections::{VecMap, VecSet};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::Subs;
|
||||
use roc_types::subs::Variable;
|
||||
use roc_types::types::{Category, PatternCategory};
|
||||
use roc_unify::unify::MustImplementAbility;
|
||||
use roc_types::subs::{Content, FlatType, GetSubsSlice, Subs, Variable};
|
||||
use roc_types::types::{AliasKind, Category, ErrorType, PatternCategory};
|
||||
use roc_unify::unify::MustImplementConstraints;
|
||||
use roc_unify::unify::{MustImplementAbility, Obligated};
|
||||
|
||||
use crate::solve::instantiate_rigids;
|
||||
use crate::solve::{IncompleteAbilityImplementation, TypeError};
|
||||
use crate::solve::TypeError;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AbilityImplError {
|
||||
|
@ -21,33 +21,51 @@ pub enum AbilityImplError {
|
|||
BadPattern(Region, PatternCategory, Variable),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum UnderivableReason {
|
||||
NotABuiltin,
|
||||
/// The surface type is not derivable
|
||||
SurfaceNotDerivable,
|
||||
/// A nested type is not derivable
|
||||
NestedNotDerivable(ErrorType),
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub enum Unfulfilled {
|
||||
/// Incomplete custom implementation for an ability by an opaque type.
|
||||
Incomplete {
|
||||
typ: Symbol,
|
||||
ability: Symbol,
|
||||
specialized_members: Vec<Loc<Symbol>>,
|
||||
missing_members: Vec<Loc<Symbol>>,
|
||||
},
|
||||
/// Cannot derive implementation of an ability for a type.
|
||||
Underivable {
|
||||
typ: ErrorType,
|
||||
ability: Symbol,
|
||||
reason: UnderivableReason,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct DeferredMustImplementAbility(Vec<(MustImplementConstraints, AbilityImplError)>);
|
||||
pub struct DeferredMustImplementAbility {
|
||||
obligations: Vec<(MustImplementConstraints, AbilityImplError)>,
|
||||
}
|
||||
|
||||
impl DeferredMustImplementAbility {
|
||||
pub fn add(&mut self, must_implement: MustImplementConstraints, on_error: AbilityImplError) {
|
||||
self.0.push((must_implement, on_error));
|
||||
self.obligations.push((must_implement, on_error));
|
||||
}
|
||||
|
||||
pub fn check(self, subs: &mut Subs, abilities_store: &AbilitiesStore) -> Vec<TypeError> {
|
||||
let mut good = vec![];
|
||||
let mut bad = vec![];
|
||||
|
||||
macro_rules! is_good {
|
||||
($e:expr) => {
|
||||
good.contains($e)
|
||||
};
|
||||
}
|
||||
macro_rules! get_bad {
|
||||
($e:expr) => {
|
||||
bad.iter()
|
||||
.find(|(cand, _)| $e == *cand)
|
||||
.map(|(_, incomplete)| incomplete)
|
||||
};
|
||||
}
|
||||
|
||||
pub fn check_all(self, subs: &mut Subs, abilities_store: &AbilitiesStore) -> Vec<TypeError> {
|
||||
let mut problems = vec![];
|
||||
|
||||
let mut obligation_cache = ObligationCache {
|
||||
abilities_store,
|
||||
unfulfilled: Default::default(),
|
||||
checked: VecSet::with_capacity(self.obligations.len()),
|
||||
};
|
||||
|
||||
// Keep track of which types that have an incomplete ability were reported as part of
|
||||
// another type error (from an expression or pattern). If we reported an error for a type
|
||||
// that doesn't implement an ability in that context, we don't want to repeat the error
|
||||
|
@ -55,44 +73,13 @@ impl DeferredMustImplementAbility {
|
|||
let mut reported_in_context = vec![];
|
||||
let mut incomplete_not_in_context = vec![];
|
||||
|
||||
for (constraints, on_error) in self.0.into_iter() {
|
||||
for (constraints, on_error) in self.obligations.into_iter() {
|
||||
let must_implement = constraints.get_unique();
|
||||
|
||||
// First off, make sure we populate information about which of the "must implement"
|
||||
// constraints are met, and which aren't.
|
||||
for &mia @ MustImplementAbility { typ, ability } in must_implement.iter() {
|
||||
if is_good!(&mia) || get_bad!(mia).is_some() {
|
||||
continue;
|
||||
}
|
||||
|
||||
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() {
|
||||
good.push(mia);
|
||||
} else {
|
||||
bad.push((
|
||||
mia,
|
||||
IncompleteAbilityImplementation {
|
||||
typ,
|
||||
ability,
|
||||
specialized_members,
|
||||
missing_members,
|
||||
},
|
||||
));
|
||||
}
|
||||
for mia in must_implement.iter() {
|
||||
obligation_cache.check_one(subs, *mia);
|
||||
}
|
||||
|
||||
// Now, figure out what errors we need to report.
|
||||
|
@ -107,12 +94,13 @@ impl DeferredMustImplementAbility {
|
|||
incomplete_not_in_context.extend(must_implement);
|
||||
}
|
||||
BadExpr(region, category, var) => {
|
||||
let incomplete_types = must_implement
|
||||
let unfulfilled = must_implement
|
||||
.iter()
|
||||
.filter_map(|e| get_bad!(*e))
|
||||
.filter_map(|mia| obligation_cache.unfulfilled.get(mia))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if !incomplete_types.is_empty() {
|
||||
|
||||
if !unfulfilled.is_empty() {
|
||||
// Demote the bad variable that exposed this problem to an error, both so
|
||||
// that we have an ErrorType to report and so that codegen knows to deal
|
||||
// with the error later.
|
||||
|
@ -121,18 +109,19 @@ impl DeferredMustImplementAbility {
|
|||
region,
|
||||
category,
|
||||
error_type,
|
||||
incomplete_types,
|
||||
unfulfilled,
|
||||
));
|
||||
reported_in_context.extend(must_implement);
|
||||
}
|
||||
}
|
||||
BadPattern(region, category, var) => {
|
||||
let incomplete_types = must_implement
|
||||
let unfulfilled = must_implement
|
||||
.iter()
|
||||
.filter_map(|e| get_bad!(*e))
|
||||
.filter_map(|mia| obligation_cache.unfulfilled.get(mia))
|
||||
.cloned()
|
||||
.collect::<Vec<_>>();
|
||||
if !incomplete_types.is_empty() {
|
||||
|
||||
if !unfulfilled.is_empty() {
|
||||
// Demote the bad variable that exposed this problem to an error, both so
|
||||
// that we have an ErrorType to report and so that codegen knows to deal
|
||||
// with the error later.
|
||||
|
@ -141,7 +130,7 @@ impl DeferredMustImplementAbility {
|
|||
region,
|
||||
category,
|
||||
error_type,
|
||||
incomplete_types,
|
||||
unfulfilled,
|
||||
));
|
||||
reported_in_context.extend(must_implement);
|
||||
}
|
||||
|
@ -152,11 +141,9 @@ impl DeferredMustImplementAbility {
|
|||
// Go through and attach generic "type does not implement ability" errors, if they were not
|
||||
// part of a larger context.
|
||||
for mia in incomplete_not_in_context.into_iter() {
|
||||
if let Some(must_implement) = get_bad!(mia) {
|
||||
if let Some(unfulfilled) = obligation_cache.unfulfilled.get(&mia) {
|
||||
if !reported_in_context.contains(&mia) {
|
||||
problems.push(TypeError::IncompleteAbilityImplementation(
|
||||
must_implement.clone(),
|
||||
));
|
||||
problems.push(TypeError::UnfulfilledAbility(unfulfilled.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -165,6 +152,187 @@ impl DeferredMustImplementAbility {
|
|||
}
|
||||
}
|
||||
|
||||
struct ObligationCache<'a> {
|
||||
abilities_store: &'a AbilitiesStore,
|
||||
|
||||
checked: VecSet<MustImplementAbility>,
|
||||
unfulfilled: VecMap<MustImplementAbility, Unfulfilled>,
|
||||
}
|
||||
|
||||
impl ObligationCache<'_> {
|
||||
fn check_one(&mut self, subs: &mut Subs, mia: MustImplementAbility) {
|
||||
if self.checked.contains(&mia) {
|
||||
return;
|
||||
}
|
||||
|
||||
let MustImplementAbility { typ, ability } = mia;
|
||||
|
||||
match typ {
|
||||
Obligated::Opaque(typ) => {
|
||||
let members_of_ability = self.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 self.abilities_store.get_specialization(member, typ) {
|
||||
None => {
|
||||
let root_data = self.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() {
|
||||
self.unfulfilled.insert(
|
||||
mia,
|
||||
Unfulfilled::Incomplete {
|
||||
typ,
|
||||
ability,
|
||||
specialized_members,
|
||||
missing_members,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Obligated::Adhoc(var) => {
|
||||
let opt_can_derive_builtin = match ability {
|
||||
Symbol::ENCODE_ENCODING => Some(self.can_derive_encoding(subs, var)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let opt_underivable = match opt_can_derive_builtin {
|
||||
Some(Ok(())) => {
|
||||
// can derive!
|
||||
None
|
||||
}
|
||||
Some(Err(failure_var)) => Some(if failure_var == var {
|
||||
UnderivableReason::SurfaceNotDerivable
|
||||
} else {
|
||||
let (error_type, _skeletons) = subs.var_to_error_type(failure_var);
|
||||
UnderivableReason::NestedNotDerivable(error_type)
|
||||
}),
|
||||
None => Some(UnderivableReason::NotABuiltin),
|
||||
};
|
||||
|
||||
if let Some(underivable_reason) = opt_underivable {
|
||||
let (error_type, _skeletons) = subs.var_to_error_type(var);
|
||||
self.unfulfilled.insert(
|
||||
mia,
|
||||
Unfulfilled::Underivable {
|
||||
typ: error_type,
|
||||
ability,
|
||||
reason: underivable_reason,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.checked.insert(mia);
|
||||
}
|
||||
|
||||
// If we have a lot of these, consider using a visitor.
|
||||
// It will be very similar for most types (can't derive functions, can't derive unbound type
|
||||
// variables, can only derive opaques if they have an impl, etc).
|
||||
fn can_derive_encoding(&mut self, subs: &mut Subs, var: Variable) -> Result<(), Variable> {
|
||||
let mut stack = vec![var];
|
||||
let mut seen_recursion_vars = vec![];
|
||||
|
||||
macro_rules! push_var_slice {
|
||||
($slice:expr) => {
|
||||
stack.extend(subs.get_subs_slice($slice))
|
||||
};
|
||||
}
|
||||
|
||||
while let Some(var) = stack.pop() {
|
||||
if seen_recursion_vars.contains(&var) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let content = subs.get_content_without_compacting(var);
|
||||
|
||||
use Content::*;
|
||||
use FlatType::*;
|
||||
match content {
|
||||
FlexVar(_) | RigidVar(_) => return Err(var),
|
||||
FlexAbleVar(_, ability) | RigidAbleVar(_, ability) => {
|
||||
if *ability != Symbol::ENCODE_ENCODING {
|
||||
return Err(var);
|
||||
}
|
||||
// Any concrete type this variables is instantiated with will also gain a "does
|
||||
// implement" check so this is okay.
|
||||
}
|
||||
RecursionVar {
|
||||
structure,
|
||||
opt_name: _,
|
||||
} => {
|
||||
seen_recursion_vars.push(var);
|
||||
stack.push(*structure);
|
||||
}
|
||||
Structure(flat_type) => match flat_type {
|
||||
Apply(
|
||||
Symbol::LIST_LIST | Symbol::SET_SET | Symbol::DICT_DICT | Symbol::STR_STR,
|
||||
vars,
|
||||
) => push_var_slice!(*vars),
|
||||
Apply(_, ..) => return Err(var),
|
||||
Func(..) => {
|
||||
return Err(var);
|
||||
}
|
||||
Record(fields, var) => {
|
||||
push_var_slice!(fields.variables());
|
||||
stack.push(*var);
|
||||
}
|
||||
TagUnion(tags, ext_var) => {
|
||||
for i in tags.variables() {
|
||||
push_var_slice!(subs[i]);
|
||||
}
|
||||
stack.push(*ext_var);
|
||||
}
|
||||
FunctionOrTagUnion(_, _, var) => stack.push(*var),
|
||||
RecursiveTagUnion(rec_var, tags, ext_var) => {
|
||||
seen_recursion_vars.push(*rec_var);
|
||||
for i in tags.variables() {
|
||||
push_var_slice!(subs[i]);
|
||||
}
|
||||
stack.push(*ext_var);
|
||||
}
|
||||
EmptyRecord | EmptyTagUnion => {
|
||||
// yes
|
||||
}
|
||||
Erroneous(_) => return Err(var),
|
||||
},
|
||||
Alias(name, _, _, AliasKind::Opaque) => {
|
||||
let mia = MustImplementAbility {
|
||||
typ: Obligated::Opaque(*name),
|
||||
ability: Symbol::ENCODE_ENCODING,
|
||||
};
|
||||
|
||||
self.check_one(subs, mia);
|
||||
|
||||
if self.unfulfilled.contains_key(&mia) {
|
||||
return Err(var);
|
||||
}
|
||||
}
|
||||
Alias(_, arguments, real_type_var, _) => {
|
||||
push_var_slice!(arguments.all_variables());
|
||||
stack.push(*real_type_var);
|
||||
}
|
||||
RangedNumber(..) => {
|
||||
// yes, all numbers can
|
||||
}
|
||||
Error => {
|
||||
return Err(var);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Determines what type implements an ability member of a specialized signature, given the
|
||||
/// [MustImplementAbility] constraints of the signature.
|
||||
pub fn type_implementing_member(
|
||||
|
@ -186,7 +354,10 @@ pub fn type_implementing_member(
|
|||
specialization_must_implement_constraints
|
||||
.iter_for_ability(ability)
|
||||
.next()
|
||||
.map(|mia| mia.typ)
|
||||
.map(|mia| match mia.typ {
|
||||
Obligated::Opaque(symbol) => symbol,
|
||||
Obligated::Adhoc(_) => internal_error!("Variable ended up in specialization signature"),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resolve_ability_specialization(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue