Validate derives clauses after solving

This commit is contained in:
Ayaz Hafiz 2022-05-20 15:53:47 -04:00
parent 4cf87510c8
commit a4c122d5db
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
11 changed files with 440 additions and 113 deletions

View file

@ -1,15 +1,16 @@
use roc_can::abilities::AbilitiesStore;
use roc_can::expr::PendingDerives;
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::{Content, FlatType, GetSubsSlice, Subs, Variable};
use roc_types::subs::{Content, FlatType, GetSubsSlice, Rank, 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::TypeError;
use crate::solve::{instantiate_rigids, type_to_var};
use crate::solve::{Aliases, Pools, TypeError};
#[derive(Debug, Clone)]
pub enum AbilityImplError {
@ -38,33 +39,138 @@ pub enum Unfulfilled {
ability: Symbol,
missing_members: Vec<Loc<Symbol>>,
},
/// Cannot derive implementation of an ability for a type.
Underivable {
/// Cannot derive implementation of an ability for a structural type.
AdhocUnderivable {
typ: ErrorType,
ability: Symbol,
reason: UnderivableReason,
},
/// Cannot derive implementation of an ability for an opaque type.
OpaqueUnderivable {
typ: ErrorType,
ability: Symbol,
opaque: Symbol,
derive_region: Region,
reason: UnderivableReason,
},
}
#[derive(Default, Debug)]
pub struct DeferredMustImplementAbility {
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct DeriveKey {
pub opaque: Symbol,
pub ability: Symbol,
}
#[derive(Debug)]
pub struct PendingDerivesTable(
/// derive key -> (opaque type var to use for checking, derive region)
VecMap<DeriveKey, (Variable, Region)>,
);
impl PendingDerivesTable {
pub fn new(subs: &mut Subs, aliases: &mut Aliases, pending_derives: PendingDerives) -> Self {
let mut table = VecMap::with_capacity(pending_derives.len());
for (opaque, (typ, derives)) in pending_derives.into_iter() {
for Loc {
value: ability,
region,
} in derives
{
debug_assert!(
ability.is_builtin_ability(),
"Not a builtin - should have been caught during can"
);
let derive_key = DeriveKey { opaque, ability };
// Neither rank nor pools should matter here.
let opaque_var =
type_to_var(subs, Rank::toplevel(), &mut Pools::default(), aliases, &typ);
let real_var = match subs.get_content_without_compacting(opaque_var) {
Content::Alias(_, _, real_var, AliasKind::Opaque) => real_var,
_ => internal_error!("Non-opaque in derives table"),
};
let old = table.insert(derive_key, (*real_var, region));
debug_assert!(old.is_none());
}
}
Self(table)
}
}
#[derive(Debug)]
pub struct DeferredObligations {
/// Obligations, to be filled in during solving of a module.
obligations: Vec<(MustImplementConstraints, AbilityImplError)>,
/// Derives that module-defined opaques claim to have.
pending_derives: PendingDerivesTable,
/// Derives that are claimed, but have also been determined to have
/// specializations.
dominated_derives: VecSet<DeriveKey>,
}
impl DeferredMustImplementAbility {
impl DeferredObligations {
pub fn new(pending_derives: PendingDerivesTable) -> Self {
Self {
obligations: Default::default(),
pending_derives,
dominated_derives: Default::default(),
}
}
pub fn add(&mut self, must_implement: MustImplementConstraints, on_error: AbilityImplError) {
self.obligations.push((must_implement, on_error));
}
pub fn check_all(self, subs: &mut Subs, abilities_store: &AbilitiesStore) -> Vec<TypeError> {
pub fn dominate(&mut self, key: DeriveKey) {
if self.pending_derives.0.contains_key(&key) {
self.dominated_derives.insert(key);
}
}
// Rules for checking ability implementations:
// - Ad-hoc derives for structural types are checked on-the-fly
// - Opaque derives are registered as "pending" when we check a module
// - Opaque derives are always checked and registered at the end to make sure opaque
// specializations are found first
// - If an opaque O both derives and specializes an ability A
// - The specialization is recorded in the abilities store (this is done in solve/solve)
// - The derive is checked, but will not be recorded in the abilities store (this is done here)
// - Obligations for O to implement A will defer to whether the specialization is complete
pub fn check_all(
self,
subs: &mut Subs,
abilities_store: &AbilitiesStore,
) -> (Vec<TypeError>, Vec<DeriveKey>) {
let mut problems = vec![];
let Self {
obligations,
pending_derives,
dominated_derives,
} = self;
let mut obligation_cache = ObligationCache {
abilities_store,
unfulfilled: Default::default(),
checked: VecSet::with_capacity(self.obligations.len()),
pending_derives: &pending_derives,
dominated_derives: &dominated_derives,
cache: VecMap::with_capacity(obligations.len()),
};
let mut legal_derives = Vec::with_capacity(pending_derives.0.len());
// First, check all derives.
for (&derive_key, &(opaque_real_var, derive_region)) in pending_derives.0.iter() {
let result =
obligation_cache.check_derive(subs, derive_key, opaque_real_var, derive_region);
match result {
Ok(()) => legal_derives.push(derive_key),
Err(problem) => problems.push(TypeError::UnfulfilledAbility(problem)),
}
}
// 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
@ -72,16 +178,22 @@ impl DeferredMustImplementAbility {
let mut reported_in_context = vec![];
let mut incomplete_not_in_context = vec![];
for (constraints, on_error) in self.obligations.into_iter() {
for (constraints, on_error) in 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 in must_implement.iter() {
obligation_cache.check_one(subs, *mia);
}
let mut get_unfulfilled = |must_implement: &[MustImplementAbility]| {
must_implement
.iter()
.filter_map(|mia| {
obligation_cache
.check_one(subs, *mia)
.as_ref()
.err()
.cloned()
})
.collect::<Vec<_>>()
};
// Now, figure out what errors we need to report.
use AbilityImplError::*;
match on_error {
IncompleteAbility => {
@ -93,11 +205,7 @@ impl DeferredMustImplementAbility {
incomplete_not_in_context.extend(must_implement);
}
BadExpr(region, category, var) => {
let unfulfilled = must_implement
.iter()
.filter_map(|mia| obligation_cache.unfulfilled.get(mia))
.cloned()
.collect::<Vec<_>>();
let unfulfilled = get_unfulfilled(&must_implement);
if !unfulfilled.is_empty() {
// Demote the bad variable that exposed this problem to an error, both so
@ -114,11 +222,7 @@ impl DeferredMustImplementAbility {
}
}
BadPattern(region, category, var) => {
let unfulfilled = must_implement
.iter()
.filter_map(|mia| obligation_cache.unfulfilled.get(mia))
.cloned()
.collect::<Vec<_>>();
let unfulfilled = get_unfulfilled(&must_implement);
if !unfulfilled.is_empty() {
// Demote the bad variable that exposed this problem to an error, both so
@ -140,56 +244,83 @@ 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(unfulfilled) = obligation_cache.unfulfilled.get(&mia) {
if let Err(unfulfilled) = obligation_cache.check_one(subs, mia) {
if !reported_in_context.contains(&mia) {
problems.push(TypeError::UnfulfilledAbility(unfulfilled.clone()));
}
}
}
problems
(problems, legal_derives)
}
}
type ObligationResult = Result<(), Unfulfilled>;
struct ObligationCache<'a> {
abilities_store: &'a AbilitiesStore,
dominated_derives: &'a VecSet<DeriveKey>,
pending_derives: &'a PendingDerivesTable,
checked: VecSet<MustImplementAbility>,
unfulfilled: VecMap<MustImplementAbility, Unfulfilled>,
cache: VecMap<MustImplementAbility, Result<(), Unfulfilled>>,
}
impl ObligationCache<'_> {
fn check_one(&mut self, subs: &mut Subs, mia: MustImplementAbility) {
if self.checked.contains(&mia) {
fn check_one(&mut self, subs: &mut Subs, mia: MustImplementAbility) -> &ObligationResult {
self.check_one_help(subs, mia);
self.cache.get(&mia).unwrap()
}
fn check_one_help(&mut self, subs: &mut Subs, mia: MustImplementAbility) {
if let Some(_) = self.cache.get(&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 missing_members = Vec::new();
for &member in members_of_ability {
if self
.abilities_store
.get_specialization(member, typ)
.is_none()
{
let root_data = self.abilities_store.member_def(member).unwrap();
missing_members.push(Loc::at(root_data.region, member));
}
}
let obligation_result = match typ {
Obligated::Opaque(opaque) => {
let derive_key = DeriveKey { opaque, ability };
if !missing_members.is_empty() {
self.unfulfilled.insert(
mia,
Unfulfilled::Incomplete {
typ,
let check_specialization = || {
let members_of_ability =
self.abilities_store.members_of_ability(ability).unwrap();
let mut missing_members = Vec::new();
for &member in members_of_ability {
if self
.abilities_store
.get_specialization(member, opaque)
.is_none()
{
let root_data = self.abilities_store.member_def(member).unwrap();
missing_members.push(Loc::at(root_data.region, member));
}
}
if !missing_members.is_empty() {
Err(Unfulfilled::Incomplete {
typ: opaque,
ability,
missing_members,
},
);
})
} else {
Ok(())
}
};
if self.dominated_derives.contains(&derive_key) {
// Always check the specialization if it dominates. The derive will be
// checked out-of-band.
check_specialization()
} else {
match self.pending_derives.0.get(&derive_key) {
// Derive and no known specialization; check the derive
Some((opaque_real_var, derive_region)) => {
self.check_derive(subs, derive_key, *opaque_real_var, *derive_region)
}
// No derive, this must be a specialization at best
None => check_specialization(),
}
}
}
@ -215,19 +346,98 @@ impl ObligationCache<'_> {
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,
},
);
Err(Unfulfilled::AdhocUnderivable {
typ: error_type,
ability,
reason: underivable_reason,
})
} else {
Ok(())
}
}
}
};
self.checked.insert(mia);
self.cache.insert(mia, obligation_result);
}
fn check_derive(
&mut self,
subs: &mut Subs,
derive_key: DeriveKey,
opaque_real_var: Variable,
derive_region: Region,
) -> ObligationResult {
// Checking of derives needs to be treated a specially. We suppose that the derive holds,
// in case the opaque type is recursive; then, we check that the opaque's real type is
// indeed derivable.
let root_obligation = MustImplementAbility {
typ: Obligated::Opaque(derive_key.opaque),
ability: derive_key.ability,
};
let fake_is_fulfilled = Ok(());
// If this opaque both derives and specializes, we may already know whether the
// specialization fulfills or not. Since specializations take priority over derives, we
// want to keep that result around.
let opt_specialization_result = self
.cache
.insert(root_obligation, fake_is_fulfilled.clone());
let is_dominated = self.dominated_derives.contains(&derive_key);
debug_assert!(
opt_specialization_result.is_none() || is_dominated,
"This derive also has a specialization but it's not marked as dominated!"
);
// Now we check whether the structural type behind the opaque is derivable, since that's
// what we'll need to generate an implementation for during codegen.
let real_var_obligation = MustImplementAbility {
typ: Obligated::Adhoc(opaque_real_var),
ability: derive_key.ability,
};
let real_var_result = self.check_one(subs, real_var_obligation).as_ref();
let root_result = real_var_result
.map_err(|err| match err {
// Promote the failure, which should be related to a structural type not being
// derivable for the ability, to a failure regarding the opaque in particular.
Unfulfilled::AdhocUnderivable {
typ,
ability,
reason,
} => Unfulfilled::OpaqueUnderivable {
typ: typ.clone(),
ability: *ability,
reason: reason.clone(),
opaque: derive_key.opaque,
derive_region,
},
_ => internal_error!("unexpected underivable result"),
})
.map(|&()| ());
if is_dominated {
// Remove the derive result because the specialization check should take priority.
let check_has_fake = self.cache.remove(&root_obligation);
debug_assert_eq!(check_has_fake.map(|(_, b)| b), Some(fake_is_fulfilled));
if let Some(specialization_result) = opt_specialization_result {
self.cache.insert(root_obligation, specialization_result);
} else {
// Even if we don't yet know whether the specialization fulfills or not, we
// remove the result of the derive. Because it's dominated, the next time we
// look at this opaque obligation, the specialization will be checked.
}
root_result
} else {
// Make sure we fix-up with the correct result of the check.
let check_has_fake = self.cache.insert(root_obligation, root_result);
debug_assert_eq!(check_has_fake, Some(fake_is_fulfilled));
self.cache.get(&root_obligation).unwrap().clone()
}
}
// If we have a lot of these, consider using a visitor.
@ -306,9 +516,9 @@ impl ObligationCache<'_> {
ability: Symbol::ENCODE_ENCODING,
};
self.check_one(subs, mia);
self.check_one_help(subs, mia);
if self.unfulfilled.contains_key(&mia) {
if let Some(Err(_)) = self.cache.get(&mia) {
return Err(var);
}
}