mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-21 07:49:17 +03:00
Validate derives clauses after solving
This commit is contained in:
parent
4cf87510c8
commit
a4c122d5db
@ -32,6 +32,7 @@ use roc_problem::can::{CycleEntry, Problem, RuntimeError};
|
||||
use roc_region::all::{Loc, Region};
|
||||
use roc_types::subs::IllegalCycleMark;
|
||||
use roc_types::subs::{VarStore, Variable};
|
||||
use roc_types::types::AliasCommon;
|
||||
use roc_types::types::AliasKind;
|
||||
use roc_types::types::AliasVar;
|
||||
use roc_types::types::LambdaSet;
|
||||
@ -349,7 +350,7 @@ fn canonicalize_opaque<'a>(
|
||||
ann: &'a Loc<ast::TypeAnnotation<'a>>,
|
||||
vars: &[Loc<Lowercase>],
|
||||
derives: Option<&'a Loc<ast::Derived<'a>>>,
|
||||
) -> Result<(Alias, Vec<(Symbol, Region)>), ()> {
|
||||
) -> Result<Alias, ()> {
|
||||
let alias = canonicalize_alias(
|
||||
env,
|
||||
output,
|
||||
@ -381,7 +382,7 @@ fn canonicalize_opaque<'a>(
|
||||
Type::Apply(ability, args, _)
|
||||
if ability.is_builtin_ability() && args.is_empty() =>
|
||||
{
|
||||
can_derives.push((ability, region));
|
||||
can_derives.push(Loc::at(region, ability));
|
||||
}
|
||||
_ => {
|
||||
// Register the problem but keep going, we may still be able to compile the
|
||||
@ -391,10 +392,30 @@ fn canonicalize_opaque<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
Ok((alias, can_derives))
|
||||
} else {
|
||||
Ok((alias, vec![]))
|
||||
if !can_derives.is_empty() {
|
||||
// Fresh instance of this opaque to be checked for derivability during solving.
|
||||
let fresh_inst = Type::DelayedAlias(AliasCommon {
|
||||
symbol: name.value,
|
||||
type_arguments: alias
|
||||
.type_variables
|
||||
.iter()
|
||||
.map(|_| Type::Variable(var_store.fresh()))
|
||||
.collect(),
|
||||
lambda_set_variables: alias
|
||||
.lambda_set_variables
|
||||
.iter()
|
||||
.map(|_| LambdaSet(Type::Variable(var_store.fresh())))
|
||||
.collect(),
|
||||
});
|
||||
|
||||
let old = output
|
||||
.pending_derives
|
||||
.insert(name.value, (fresh_inst, can_derives));
|
||||
debug_assert!(old.is_none());
|
||||
}
|
||||
}
|
||||
|
||||
Ok(alias)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
@ -550,7 +571,7 @@ pub(crate) fn canonicalize_defs<'a>(
|
||||
derived,
|
||||
);
|
||||
|
||||
if let Ok((alias, _derives_to_check)) = alias_and_derives {
|
||||
if let Ok(alias) = alias_and_derives {
|
||||
aliases.insert(name.value, alias);
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,9 @@ use roc_types::types::{Alias, Category, LambdaSet, OptAbleVar, Type};
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::{char, u32};
|
||||
|
||||
/// Derives that an opaque type has claimed, to checked and recorded after solving.
|
||||
pub type PendingDerives = VecMap<Symbol, (Type, Vec<Loc<Symbol>>)>;
|
||||
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Output {
|
||||
pub references: References,
|
||||
@ -31,6 +34,7 @@ pub struct Output {
|
||||
pub introduced_variables: IntroducedVariables,
|
||||
pub aliases: VecMap<Symbol, Alias>,
|
||||
pub non_closures: VecSet<Symbol>,
|
||||
pub pending_derives: PendingDerives,
|
||||
}
|
||||
|
||||
impl Output {
|
||||
@ -45,6 +49,15 @@ impl Output {
|
||||
.union_owned(other.introduced_variables);
|
||||
self.aliases.extend(other.aliases);
|
||||
self.non_closures.extend(other.non_closures);
|
||||
|
||||
{
|
||||
let expected_derives_size = self.pending_derives.len() + other.pending_derives.len();
|
||||
self.pending_derives.extend(other.pending_derives);
|
||||
debug_assert!(
|
||||
expected_derives_size == self.pending_derives.len(),
|
||||
"Derives overwritten from nested scope - something is very wrong"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ use crate::annotation::canonicalize_annotation;
|
||||
use crate::def::{canonicalize_defs, sort_can_defs, Declaration, Def};
|
||||
use crate::effect_module::HostedGeneratedFunctions;
|
||||
use crate::env::Env;
|
||||
use crate::expr::{ClosureData, Expr, Output};
|
||||
use crate::expr::{ClosureData, Expr, Output, PendingDerives};
|
||||
use crate::operator::desugar_def;
|
||||
use crate::pattern::Pattern;
|
||||
use crate::scope::Scope;
|
||||
@ -51,6 +51,7 @@ pub struct ModuleOutput {
|
||||
pub referenced_values: VecSet<Symbol>,
|
||||
pub referenced_types: VecSet<Symbol>,
|
||||
pub symbols_from_requires: Vec<(Loc<Symbol>, Loc<Type>)>,
|
||||
pub pending_derives: PendingDerives,
|
||||
pub scope: Scope,
|
||||
}
|
||||
|
||||
@ -290,6 +291,8 @@ pub fn canonicalize_module_defs<'a>(
|
||||
PatternType::TopLevelDef,
|
||||
);
|
||||
|
||||
let pending_derives = output.pending_derives;
|
||||
|
||||
// See if any of the new idents we defined went unused.
|
||||
// If any were unused and also not exposed, report it.
|
||||
for (symbol, region) in symbols_introduced {
|
||||
@ -354,6 +357,11 @@ pub fn canonicalize_module_defs<'a>(
|
||||
|
||||
let (mut declarations, mut output) = sort_can_defs(&mut env, var_store, defs, new_output);
|
||||
|
||||
debug_assert!(
|
||||
output.pending_derives.is_empty(),
|
||||
"I thought pending derives are only found during def introduction"
|
||||
);
|
||||
|
||||
let symbols_from_requires = symbols_from_requires
|
||||
.iter()
|
||||
.map(|(symbol, loc_ann)| {
|
||||
@ -588,6 +596,7 @@ pub fn canonicalize_module_defs<'a>(
|
||||
exposed_imports: can_exposed_imports,
|
||||
problems: env.problems,
|
||||
symbols_from_requires,
|
||||
pending_derives,
|
||||
lookups,
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ impl<K: PartialEq, V> VecMap<K, V> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<K: Ord, V> Extend<(K, V)> for VecMap<K, V> {
|
||||
impl<K: PartialEq, V> Extend<(K, V)> for VecMap<K, V> {
|
||||
#[inline(always)]
|
||||
fn extend<T: IntoIterator<Item = (K, V)>>(&mut self, iter: T) {
|
||||
let it = iter.into_iter();
|
||||
|
@ -9,6 +9,7 @@ use roc_builtins::std::borrow_stdlib;
|
||||
use roc_can::abilities::{AbilitiesStore, SolvedSpecializations};
|
||||
use roc_can::constraint::{Constraint as ConstraintSoa, Constraints};
|
||||
use roc_can::def::Declaration;
|
||||
use roc_can::expr::PendingDerives;
|
||||
use roc_can::module::{canonicalize_module_defs, Module};
|
||||
use roc_collections::{default_hasher, BumpMap, MutMap, MutSet, VecMap, VecSet};
|
||||
use roc_constrain::module::{
|
||||
@ -366,6 +367,7 @@ fn start_phase<'a>(
|
||||
imported_modules,
|
||||
declarations,
|
||||
dep_idents,
|
||||
pending_derives,
|
||||
..
|
||||
} = constrained;
|
||||
|
||||
@ -375,6 +377,7 @@ fn start_phase<'a>(
|
||||
module_timing,
|
||||
constraints,
|
||||
constraint,
|
||||
pending_derives,
|
||||
var_store,
|
||||
imported_modules,
|
||||
&state.exposed_types,
|
||||
@ -536,6 +539,9 @@ struct ConstrainedModule {
|
||||
var_store: VarStore,
|
||||
dep_idents: IdentIdsByModule,
|
||||
module_timing: ModuleTiming,
|
||||
// Rather than adding pending derives as constraints, hand them directly to solve because they
|
||||
// must be solved at the end of a module.
|
||||
pending_derives: PendingDerives,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -885,6 +891,7 @@ enum BuildTask<'a> {
|
||||
module_timing: ModuleTiming,
|
||||
constraints: Constraints,
|
||||
constraint: ConstraintSoa,
|
||||
pending_derives: PendingDerives,
|
||||
var_store: VarStore,
|
||||
declarations: Vec<Declaration>,
|
||||
dep_idents: IdentIdsByModule,
|
||||
@ -3500,6 +3507,7 @@ impl<'a> BuildTask<'a> {
|
||||
module_timing: ModuleTiming,
|
||||
constraints: Constraints,
|
||||
constraint: ConstraintSoa,
|
||||
pending_derives: PendingDerives,
|
||||
var_store: VarStore,
|
||||
imported_modules: MutMap<ModuleId, Region>,
|
||||
exposed_types: &ExposedByModule,
|
||||
@ -3542,6 +3550,7 @@ impl<'a> BuildTask<'a> {
|
||||
exposed_for_module,
|
||||
constraints,
|
||||
constraint,
|
||||
pending_derives,
|
||||
var_store,
|
||||
declarations,
|
||||
dep_idents,
|
||||
@ -3616,6 +3625,7 @@ fn run_solve_solve(
|
||||
exposed_for_module: ExposedForModule,
|
||||
mut constraints: Constraints,
|
||||
constraint: ConstraintSoa,
|
||||
pending_derives: PendingDerives,
|
||||
mut var_store: VarStore,
|
||||
module: Module,
|
||||
) -> (
|
||||
@ -3663,6 +3673,7 @@ fn run_solve_solve(
|
||||
subs,
|
||||
solve_aliases,
|
||||
abilities_store,
|
||||
pending_derives,
|
||||
);
|
||||
|
||||
let module_id = module.module_id;
|
||||
@ -3713,6 +3724,7 @@ fn run_solve<'a>(
|
||||
exposed_for_module: ExposedForModule,
|
||||
constraints: Constraints,
|
||||
constraint: ConstraintSoa,
|
||||
pending_derives: PendingDerives,
|
||||
var_store: VarStore,
|
||||
decls: Vec<Declaration>,
|
||||
dep_idents: IdentIdsByModule,
|
||||
@ -3733,6 +3745,7 @@ fn run_solve<'a>(
|
||||
exposed_for_module,
|
||||
constraints,
|
||||
constraint,
|
||||
pending_derives,
|
||||
var_store,
|
||||
module,
|
||||
),
|
||||
@ -3754,6 +3767,7 @@ fn run_solve<'a>(
|
||||
exposed_for_module,
|
||||
constraints,
|
||||
constraint,
|
||||
pending_derives,
|
||||
var_store,
|
||||
module,
|
||||
)
|
||||
@ -3987,6 +4001,7 @@ fn canonicalize_and_constrain<'a>(
|
||||
ident_ids: module_output.scope.locals.ident_ids,
|
||||
dep_idents,
|
||||
module_timing,
|
||||
pending_derives: module_output.pending_derives,
|
||||
};
|
||||
|
||||
CanAndCon {
|
||||
@ -4476,6 +4491,7 @@ fn run_task<'a>(
|
||||
exposed_for_module,
|
||||
constraints,
|
||||
constraint,
|
||||
pending_derives,
|
||||
var_store,
|
||||
ident_ids,
|
||||
declarations,
|
||||
@ -4489,6 +4505,7 @@ fn run_task<'a>(
|
||||
exposed_for_module,
|
||||
constraints,
|
||||
constraint,
|
||||
pending_derives,
|
||||
var_store,
|
||||
declarations,
|
||||
dep_idents,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
use crate::solve::{self, Aliases};
|
||||
use roc_can::abilities::{AbilitiesStore, SolvedSpecializations};
|
||||
use roc_can::constraint::{Constraint as ConstraintSoa, Constraints};
|
||||
use roc_can::expr::PendingDerives;
|
||||
use roc_can::module::RigidVariables;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_module::symbol::Symbol;
|
||||
@ -38,6 +39,7 @@ pub fn run_solve(
|
||||
mut subs: Subs,
|
||||
mut aliases: Aliases,
|
||||
mut abilities_store: AbilitiesStore,
|
||||
pending_derives: PendingDerives,
|
||||
) -> (
|
||||
Solved<Subs>,
|
||||
solve::Env,
|
||||
@ -67,6 +69,7 @@ pub fn run_solve(
|
||||
subs,
|
||||
&mut aliases,
|
||||
&constraint,
|
||||
pending_derives,
|
||||
&mut abilities_store,
|
||||
);
|
||||
|
||||
|
@ -1,12 +1,13 @@
|
||||
use crate::ability::{
|
||||
resolve_ability_specialization, type_implementing_specialization, AbilityImplError,
|
||||
DeferredMustImplementAbility, Resolved, Unfulfilled,
|
||||
DeferredObligations, DeriveKey, PendingDerivesTable, Resolved, Unfulfilled,
|
||||
};
|
||||
use bumpalo::Bump;
|
||||
use roc_can::abilities::{AbilitiesStore, MemberSpecialization};
|
||||
use roc_can::constraint::Constraint::{self, *};
|
||||
use roc_can::constraint::{Constraints, Cycle, LetConstraint, OpportunisticResolve};
|
||||
use roc_can::expected::{Expected, PExpected};
|
||||
use roc_can::expr::PendingDerives;
|
||||
use roc_collections::all::MutMap;
|
||||
use roc_debug_flags::dbg_do;
|
||||
#[cfg(debug_assertions)]
|
||||
@ -418,7 +419,7 @@ impl Env {
|
||||
const DEFAULT_POOLS: usize = 8;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Pools(Vec<Vec<Variable>>);
|
||||
pub(crate) struct Pools(Vec<Vec<Variable>>);
|
||||
|
||||
impl Default for Pools {
|
||||
fn default() -> Self {
|
||||
@ -481,6 +482,7 @@ pub fn run(
|
||||
mut subs: Subs,
|
||||
aliases: &mut Aliases,
|
||||
constraint: &Constraint,
|
||||
pending_derives: PendingDerives,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
) -> (Solved<Subs>, Env) {
|
||||
let env = run_in_place(
|
||||
@ -489,6 +491,7 @@ pub fn run(
|
||||
&mut subs,
|
||||
aliases,
|
||||
constraint,
|
||||
pending_derives,
|
||||
abilities_store,
|
||||
);
|
||||
|
||||
@ -502,6 +505,7 @@ fn run_in_place(
|
||||
subs: &mut Subs,
|
||||
aliases: &mut Aliases,
|
||||
constraint: &Constraint,
|
||||
pending_derives: PendingDerives,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
) -> Env {
|
||||
let mut pools = Pools::default();
|
||||
@ -513,7 +517,8 @@ fn run_in_place(
|
||||
let rank = Rank::toplevel();
|
||||
let arena = Bump::new();
|
||||
|
||||
let mut deferred_must_implement_abilities = DeferredMustImplementAbility::default();
|
||||
let pending_derives = PendingDerivesTable::new(subs, aliases, pending_derives);
|
||||
let mut deferred_obligations = DeferredObligations::new(pending_derives);
|
||||
|
||||
let state = solve(
|
||||
&arena,
|
||||
@ -526,12 +531,14 @@ fn run_in_place(
|
||||
subs,
|
||||
constraint,
|
||||
abilities_store,
|
||||
&mut deferred_must_implement_abilities,
|
||||
&mut deferred_obligations,
|
||||
);
|
||||
|
||||
// Now that the module has been solved, we can run through and check all
|
||||
// types claimed to implement abilities.
|
||||
problems.extend(deferred_must_implement_abilities.check_all(subs, abilities_store));
|
||||
// types claimed to implement abilities. This will also tell us what derives
|
||||
// are legal, which we need to register.
|
||||
let (obligation_problems, _derived) = deferred_obligations.check_all(subs, abilities_store);
|
||||
problems.extend(obligation_problems);
|
||||
|
||||
state.env
|
||||
}
|
||||
@ -584,7 +591,7 @@ fn solve(
|
||||
subs: &mut Subs,
|
||||
constraint: &Constraint,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
deferred_must_implement_abilities: &mut DeferredMustImplementAbility,
|
||||
deferred_obligations: &mut DeferredObligations,
|
||||
) -> State {
|
||||
let initial = Work::Constraint {
|
||||
env: &Env::default(),
|
||||
@ -644,7 +651,7 @@ fn solve(
|
||||
rank,
|
||||
abilities_store,
|
||||
problems,
|
||||
deferred_must_implement_abilities,
|
||||
deferred_obligations,
|
||||
*symbol,
|
||||
*loc_var,
|
||||
);
|
||||
@ -749,7 +756,7 @@ fn solve(
|
||||
rank,
|
||||
abilities_store,
|
||||
problems,
|
||||
deferred_must_implement_abilities,
|
||||
deferred_obligations,
|
||||
*symbol,
|
||||
*loc_var,
|
||||
);
|
||||
@ -804,7 +811,7 @@ fn solve(
|
||||
} => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
if !must_implement_ability.is_empty() {
|
||||
deferred_must_implement_abilities.add(
|
||||
deferred_obligations.add(
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadExpr(*region, category.clone(), actual),
|
||||
);
|
||||
@ -911,7 +918,7 @@ fn solve(
|
||||
} => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
if !must_implement_ability.is_empty() {
|
||||
deferred_must_implement_abilities.add(
|
||||
deferred_obligations.add(
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadExpr(
|
||||
*region,
|
||||
@ -988,7 +995,7 @@ fn solve(
|
||||
} => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
if !must_implement_ability.is_empty() {
|
||||
deferred_must_implement_abilities.add(
|
||||
deferred_obligations.add(
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadPattern(*region, category.clone(), actual),
|
||||
);
|
||||
@ -1150,7 +1157,7 @@ fn solve(
|
||||
} => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
if !must_implement_ability.is_empty() {
|
||||
deferred_must_implement_abilities.add(
|
||||
deferred_obligations.add(
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadPattern(
|
||||
*region,
|
||||
@ -1479,7 +1486,7 @@ fn check_ability_specialization(
|
||||
rank: Rank,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
problems: &mut Vec<TypeError>,
|
||||
deferred_must_implement_abilities: &mut DeferredMustImplementAbility,
|
||||
deferred_obligations: &mut DeferredObligations,
|
||||
symbol: Symbol,
|
||||
symbol_loc_var: Loc<Variable>,
|
||||
) {
|
||||
@ -1531,8 +1538,13 @@ fn check_ability_specialization(
|
||||
|
||||
// Make sure we check that the opaque has specialized all members of the
|
||||
// ability, after we finish solving the module.
|
||||
deferred_must_implement_abilities
|
||||
deferred_obligations
|
||||
.add(must_implement_ability, AbilityImplError::IncompleteAbility);
|
||||
// This specialization dominates any derives that might be present.
|
||||
deferred_obligations.dominate(DeriveKey {
|
||||
opaque,
|
||||
ability: parent_ability,
|
||||
});
|
||||
}
|
||||
Some(Obligated::Adhoc(var)) => {
|
||||
// This is a specialization of a structural type - never allowed.
|
||||
@ -1701,7 +1713,7 @@ fn either_type_index_to_var(
|
||||
}
|
||||
}
|
||||
|
||||
fn type_to_var(
|
||||
pub(crate) fn type_to_var(
|
||||
subs: &mut Subs,
|
||||
rank: Rank,
|
||||
pools: &mut Pools,
|
||||
|
@ -255,31 +255,12 @@ fn report_unfulfilled_ability<'a>(
|
||||
|
||||
alloc.stack(stack)
|
||||
}
|
||||
Unfulfilled::Underivable {
|
||||
Unfulfilled::AdhocUnderivable {
|
||||
typ,
|
||||
ability,
|
||||
reason,
|
||||
} => {
|
||||
let reason = match reason {
|
||||
UnderivableReason::NotABuiltin => {
|
||||
Some(alloc.reflow("Only builtin abilities can have generated implementations!"))
|
||||
}
|
||||
UnderivableReason::SurfaceNotDerivable => underivable_hint(alloc, ability, &typ),
|
||||
UnderivableReason::NestedNotDerivable(nested_typ) => {
|
||||
let hint = underivable_hint(alloc, ability, &nested_typ);
|
||||
let reason = alloc.stack(
|
||||
[
|
||||
alloc.reflow("In particular, an implementation for"),
|
||||
alloc.type_block(error_type_to_doc(alloc, nested_typ)),
|
||||
alloc.reflow("cannot be generated."),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(hint),
|
||||
);
|
||||
Some(reason)
|
||||
}
|
||||
};
|
||||
|
||||
let reason = report_underivable_reason(alloc, reason, ability, &typ);
|
||||
let stack = [
|
||||
alloc.concat([
|
||||
alloc.reflow("Roc can't generate an implementation of the "),
|
||||
@ -293,6 +274,61 @@ fn report_unfulfilled_ability<'a>(
|
||||
|
||||
alloc.stack(stack)
|
||||
}
|
||||
Unfulfilled::OpaqueUnderivable {
|
||||
typ,
|
||||
ability,
|
||||
opaque,
|
||||
derive_region,
|
||||
reason,
|
||||
} => {
|
||||
let reason = report_underivable_reason(alloc, reason, ability, &typ);
|
||||
let stack = [
|
||||
alloc.concat([
|
||||
alloc.reflow("Roc can't derive an implementation of the "),
|
||||
alloc.symbol_qualified(ability),
|
||||
alloc.reflow(" for "),
|
||||
alloc.symbol_unqualified(opaque),
|
||||
alloc.reflow(":"),
|
||||
]),
|
||||
alloc.region(lines.convert_region(derive_region)),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(reason)
|
||||
.chain(std::iter::once(alloc.tip().append(alloc.concat([
|
||||
alloc.reflow("You can create a custom implementation of "),
|
||||
alloc.symbol_qualified(ability),
|
||||
alloc.reflow(" for this type."),
|
||||
]))));
|
||||
|
||||
alloc.stack(stack)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn report_underivable_reason<'a>(
|
||||
alloc: &'a RocDocAllocator<'a>,
|
||||
reason: UnderivableReason,
|
||||
ability: Symbol,
|
||||
typ: &ErrorType,
|
||||
) -> Option<RocDocBuilder<'a>> {
|
||||
match reason {
|
||||
UnderivableReason::NotABuiltin => {
|
||||
Some(alloc.reflow("Only builtin abilities can have generated implementations!"))
|
||||
}
|
||||
UnderivableReason::SurfaceNotDerivable => underivable_hint(alloc, ability, &typ),
|
||||
UnderivableReason::NestedNotDerivable(nested_typ) => {
|
||||
let hint = underivable_hint(alloc, ability, &nested_typ);
|
||||
let reason = alloc.stack(
|
||||
[
|
||||
alloc.reflow("In particular, an implementation for"),
|
||||
alloc.type_block(error_type_to_doc(alloc, nested_typ)),
|
||||
alloc.reflow("cannot be generated."),
|
||||
]
|
||||
.into_iter()
|
||||
.chain(hint),
|
||||
);
|
||||
Some(reason)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::constraint::{Constraint, Constraints};
|
||||
use roc_can::env::Env;
|
||||
use roc_can::expected::Expected;
|
||||
use roc_can::expr::{canonicalize_expr, Expr, Output};
|
||||
use roc_can::expr::{canonicalize_expr, Expr, Output, PendingDerives};
|
||||
use roc_can::operator;
|
||||
use roc_can::scope::Scope;
|
||||
use roc_collections::all::{ImMap, MutMap, SendSet};
|
||||
@ -31,6 +31,7 @@ pub fn infer_expr(
|
||||
problems: &mut Vec<solve::TypeError>,
|
||||
constraints: &Constraints,
|
||||
constraint: &Constraint,
|
||||
pending_derives: PendingDerives,
|
||||
aliases: &mut Aliases,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
expr_var: Variable,
|
||||
@ -41,6 +42,7 @@ pub fn infer_expr(
|
||||
subs,
|
||||
aliases,
|
||||
constraint,
|
||||
pending_derives,
|
||||
abilities_store,
|
||||
);
|
||||
|
||||
|
@ -12,6 +12,7 @@ mod test_reporting {
|
||||
use bumpalo::Bump;
|
||||
use indoc::indoc;
|
||||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::expr::PendingDerives;
|
||||
use roc_load::{self, LoadedModule, LoadingProblem, Threading};
|
||||
use roc_module::symbol::{Interns, ModuleId};
|
||||
use roc_region::all::LineInfo;
|
||||
@ -229,6 +230,9 @@ mod test_reporting {
|
||||
&mut unify_problems,
|
||||
&constraints,
|
||||
&constraint,
|
||||
// Use `new_report_problem_as` in order to get proper derives.
|
||||
// TODO: remove the non-new reporting test infra.
|
||||
PendingDerives::default(),
|
||||
&mut solve_aliases,
|
||||
&mut abilities_store,
|
||||
var,
|
||||
|
Loading…
Reference in New Issue
Block a user