mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 16:44:33 +00:00
Validate derives clauses after solving
This commit is contained in:
parent
4cf87510c8
commit
a4c122d5db
11 changed files with 440 additions and 113 deletions
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue