Check derivability of abilities for structural types

This commit is contained in:
Ayaz Hafiz 2022-05-18 19:12:38 -04:00
parent 0e63efdf09
commit 2c87214b44
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
4 changed files with 410 additions and 167 deletions

View file

@ -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(