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

@ -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);
}
}

View File

@ -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"
);
}
}
}

View File

@ -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,
}
}

View File

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

View File

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

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);
}
}

View File

@ -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,
);

View File

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

View File

@ -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)
}
}
}

View File

@ -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,
);

View File

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