Merge pull request #3108 from rtfeldman/check-derived2

Validate derives clauses after solving
This commit is contained in:
Richard Feldman 2022-05-21 11:39:18 -04:00 committed by GitHub
commit 66bcb53eb1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 709 additions and 162 deletions

View File

@ -320,7 +320,7 @@ pub fn canonicalize_annotation(
}
}
fn make_apply_symbol(
pub(crate) fn make_apply_symbol(
env: &mut Env,
region: Region,
scope: &mut Scope,

View File

@ -3,6 +3,7 @@ use crate::abilities::MemberTypeInfo;
use crate::abilities::MemberVariables;
use crate::annotation::canonicalize_annotation;
use crate::annotation::find_type_def_symbols;
use crate::annotation::make_apply_symbol;
use crate::annotation::IntroducedVariables;
use crate::annotation::OwnedNamedOrAble;
use crate::env::Env;
@ -32,6 +33,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;
@ -231,7 +233,7 @@ fn canonicalize_alias<'a>(
output: &mut Output,
var_store: &mut VarStore,
scope: &mut Scope,
abilities_in_scope: &[Symbol],
pending_abilities_in_scope: &[Symbol],
name: Loc<Symbol>,
ann: &'a Loc<ast::TypeAnnotation<'a>>,
@ -245,7 +247,7 @@ fn canonicalize_alias<'a>(
&ann.value,
ann.region,
var_store,
abilities_in_scope,
pending_abilities_in_scope,
);
// Record all the annotation's references in output.references.lookups
@ -343,19 +345,19 @@ fn canonicalize_opaque<'a>(
output: &mut Output,
var_store: &mut VarStore,
scope: &mut Scope,
abilities_in_scope: &[Symbol],
pending_abilities_in_scope: &[Symbol],
name: Loc<Symbol>,
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,
var_store,
scope,
abilities_in_scope,
pending_abilities_in_scope,
name,
ann,
vars,
@ -369,19 +371,22 @@ fn canonicalize_opaque<'a>(
for derived in derives.items {
let region = derived.region;
let can_derived = canonicalize_annotation(
env,
scope,
&derived.value,
region,
var_store,
abilities_in_scope,
);
match can_derived.typ {
Type::Apply(ability, args, _)
if ability.is_builtin_ability() && args.is_empty() =>
{
can_derives.push((ability, region));
match derived.value.extract_spaces().item {
ast::TypeAnnotation::Apply(module_name, ident, []) => {
match make_apply_symbol(env, region, scope, module_name, ident) {
Ok(ability) if ability.is_builtin_ability() => {
can_derives.push(Loc::at(region, ability));
}
Ok(_) => {
// Register the problem but keep going, we may still be able to compile the
// program even if a derive is missing.
env.problem(Problem::IllegalDerive(region));
}
Err(_) => {
// This is bad apply; an error will have been reported for it
// already.
}
}
}
_ => {
// Register the problem but keep going, we may still be able to compile the
@ -391,10 +396,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 +575,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_collections::{VecMap, VecSet};
use roc_can::expr::PendingDerives;
use roc_collections::VecMap;
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,161 @@ 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 {
/// Indexes a deriving of an ability for an opaque type.
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct DeriveKey {
pub opaque: Symbol,
pub ability: Symbol,
}
/// Indexes a custom implementation of an ability for an opaque type.
#[derive(Debug, PartialEq, Clone, Copy)]
struct ImplKey {
opaque: Symbol,
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. Maps to the first member specialization of the same
/// ability.
dominated_derives: VecMap<DeriveKey, Region>,
}
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, impl_region: Region) {
// Only builtin abilities can be derived, and hence dominated.
if self.pending_derives.0.contains_key(&key) && !self.dominated_derives.contains_key(&key) {
self.dominated_derives.insert(key, impl_region);
}
}
// 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,
impl_cache: VecMap::with_capacity(obligations.len()),
derive_cache: VecMap::with_capacity(pending_derives.0.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() {
obligation_cache.check_derive(subs, derive_key, opaque_real_var, derive_region);
let result = obligation_cache.derive_cache.get(&derive_key).unwrap();
match result {
Ok(()) => legal_derives.push(derive_key),
Err(problem) => problems.push(TypeError::UnfulfilledAbility(problem.clone())),
}
}
for (derive_key, impl_region) in dominated_derives.iter() {
let derive_region = pending_derives.0.get(derive_key).unwrap().1;
problems.push(TypeError::DominatedDerive {
opaque: derive_key.opaque,
ability: derive_key.ability,
derive_region,
impl_region: *impl_region,
});
}
// 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 +201,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 +228,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 +245,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,94 +267,220 @@ 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 VecMap<DeriveKey, Region>,
pending_derives: &'a PendingDerivesTable,
checked: VecSet<MustImplementAbility>,
unfulfilled: VecMap<MustImplementAbility, Unfulfilled>,
impl_cache: VecMap<ImplKey, ObligationResult>,
derive_cache: VecMap<DeriveKey, ObligationResult>,
}
enum ReadCache {
Impl,
Derive,
}
impl ObligationCache<'_> {
fn check_one(&mut self, subs: &mut Subs, mia: MustImplementAbility) {
if self.checked.contains(&mia) {
return;
}
fn check_one(&mut self, subs: &mut Subs, mia: MustImplementAbility) -> ObligationResult {
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));
}
}
Obligated::Adhoc(var) => self.check_adhoc(subs, var, ability),
Obligated::Opaque(opaque) => self.check_opaque_and_read(subs, opaque, ability).clone(),
}
}
if !missing_members.is_empty() {
self.unfulfilled.insert(
mia,
Unfulfilled::Incomplete {
typ,
ability,
missing_members,
},
);
fn check_adhoc(&mut self, subs: &mut Subs, var: Variable, ability: Symbol) -> ObligationResult {
// Not worth caching ad-hoc checks because variables are unlikely to be the same between
// independent queries.
let opt_can_derive_builtin = match ability {
Symbol::ENCODE_ENCODING => Some(self.can_derive_encoding(subs, var)),
_ => None,
};
let opt_underivable = match opt_can_derive_builtin {
Some(Ok(())) => {
// can derive!
None
}
Some(Err(failure_var)) => Some(if failure_var == var {
UnderivableReason::SurfaceNotDerivable
} else {
let (error_type, _skeletons) = subs.var_to_error_type(failure_var);
UnderivableReason::NestedNotDerivable(error_type)
}),
None => Some(UnderivableReason::NotABuiltin),
};
if let Some(underivable_reason) = opt_underivable {
let (error_type, _skeletons) = subs.var_to_error_type(var);
Err(Unfulfilled::AdhocUnderivable {
typ: error_type,
ability,
reason: underivable_reason,
})
} else {
Ok(())
}
}
fn check_opaque(&mut self, subs: &mut Subs, opaque: Symbol, ability: Symbol) -> ReadCache {
let impl_key = ImplKey { opaque, ability };
let derive_key = DeriveKey { opaque, ability };
match self.pending_derives.0.get(&derive_key) {
Some(&(opaque_real_var, derive_region)) => {
if self.dominated_derives.contains_key(&derive_key) {
// We have a derive, but also a custom implementation. The custom
// implementation takes priority because we'll use that for codegen.
// We'll report an error for the conflict, and whether the derive is
// legal will be checked out-of-band.
self.check_impl(impl_key);
ReadCache::Impl
} else {
// Only a derive
self.check_derive(subs, derive_key, opaque_real_var, derive_region);
ReadCache::Derive
}
}
// Only an impl
None => {
self.check_impl(impl_key);
ReadCache::Impl
}
}
}
Obligated::Adhoc(var) => {
let opt_can_derive_builtin = match ability {
Symbol::ENCODE_ENCODING => Some(self.can_derive_encoding(subs, var)),
_ => None,
};
fn check_opaque_and_read(
&mut self,
subs: &mut Subs,
opaque: Symbol,
ability: Symbol,
) -> &ObligationResult {
match self.check_opaque(subs, opaque, ability) {
ReadCache::Impl => self.impl_cache.get(&ImplKey { opaque, ability }).unwrap(),
ReadCache::Derive => self
.derive_cache
.get(&DeriveKey { opaque, ability })
.unwrap(),
}
}
let opt_underivable = match opt_can_derive_builtin {
Some(Ok(())) => {
// can derive!
None
}
Some(Err(failure_var)) => Some(if failure_var == var {
UnderivableReason::SurfaceNotDerivable
} else {
let (error_type, _skeletons) = subs.var_to_error_type(failure_var);
UnderivableReason::NestedNotDerivable(error_type)
}),
None => Some(UnderivableReason::NotABuiltin),
};
fn check_impl(&mut self, impl_key: ImplKey) {
if self.impl_cache.get(&impl_key).is_some() {
return;
}
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,
},
);
}
let ImplKey { opaque, ability } = impl_key;
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));
}
}
self.checked.insert(mia);
let obligation_result = if !missing_members.is_empty() {
Err(Unfulfilled::Incomplete {
typ: opaque,
ability,
missing_members,
})
} else {
Ok(())
};
self.impl_cache.insert(impl_key, obligation_result);
}
fn check_derive(
&mut self,
subs: &mut Subs,
derive_key: DeriveKey,
opaque_real_var: Variable,
derive_region: Region,
) {
if self.derive_cache.get(&derive_key).is_some() {
return;
}
// The opaque may be recursive, so make sure we stop if we see it again.
// We need to enforce that on both the impl and derive cache.
let fake_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 impl_key = ImplKey {
opaque: derive_key.opaque,
ability: derive_key.ability,
};
let opt_specialization_result = self.impl_cache.insert(impl_key, fake_fulfilled.clone());
let is_dominated = self.dominated_derives.contains_key(&derive_key);
debug_assert!(
opt_specialization_result.is_none() || is_dominated,
"This derive also has a specialization but it's not marked as dominated!"
);
let old_deriving = self.derive_cache.insert(derive_key, fake_fulfilled.clone());
debug_assert!(
old_deriving.is_none(),
"Already knew deriving result, but here anyway"
);
// 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_result = self.check_adhoc(subs, opaque_real_var, derive_key.ability);
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,
ability,
reason,
opaque: derive_key.opaque,
derive_region,
},
_ => internal_error!("unexpected underivable result"),
});
// Remove the derive result because the specialization check should take priority.
let check_has_fake = self.impl_cache.remove(&impl_key);
debug_assert_eq!(check_has_fake.map(|(_, b)| b), Some(fake_fulfilled.clone()));
if let Some(specialization_result) = opt_specialization_result {
self.impl_cache.insert(impl_key, specialization_result);
}
// Make sure we fix-up with the correct result of the check.
let check_has_fake = self.derive_cache.insert(derive_key, root_result);
debug_assert_eq!(check_has_fake, Some(fake_fulfilled));
}
// If we have a lot of these, consider using a visitor.
@ -301,14 +554,11 @@ impl ObligationCache<'_> {
Erroneous(_) => return Err(var),
},
Alias(name, _, _, AliasKind::Opaque) => {
let mia = MustImplementAbility {
typ: Obligated::Opaque(*name),
ability: Symbol::ENCODE_ENCODING,
};
self.check_one(subs, mia);
if self.unfulfilled.contains_key(&mia) {
let opaque = *name;
if self
.check_opaque_and_read(subs, opaque, Symbol::ENCODE_ENCODING)
.is_err()
{
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)]
@ -96,6 +97,12 @@ pub enum TypeError {
ability: Symbol,
member: Symbol,
},
DominatedDerive {
opaque: Symbol,
ability: Symbol,
derive_region: Region,
impl_region: Region,
},
}
use roc_types::types::Alias;
@ -418,7 +425,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 +488,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 +497,7 @@ pub fn run(
&mut subs,
aliases,
constraint,
pending_derives,
abilities_store,
);
@ -502,6 +511,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 +523,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 +537,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 +597,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 +657,7 @@ fn solve(
rank,
abilities_store,
problems,
deferred_must_implement_abilities,
deferred_obligations,
*symbol,
*loc_var,
);
@ -749,7 +762,7 @@ fn solve(
rank,
abilities_store,
problems,
deferred_must_implement_abilities,
deferred_obligations,
*symbol,
*loc_var,
);
@ -804,7 +817,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 +924,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 +1001,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 +1163,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 +1492,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>,
) {
@ -1519,9 +1532,10 @@ fn check_ability_specialization(
subs.commit_snapshot(snapshot);
introduce(subs, rank, pools, &vars);
let specialization_region = symbol_loc_var.region;
let specialization = MemberSpecialization {
symbol,
region: symbol_loc_var.region,
region: specialization_region,
};
abilities_store.register_specialization_for_type(
root_symbol,
@ -1531,8 +1545,16 @@ 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,
},
specialization_region,
);
}
Some(Obligated::Adhoc(var)) => {
// This is a specialization of a structural type - never allowed.
@ -1701,7 +1723,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

@ -221,6 +221,39 @@ pub fn type_problem<'b>(
severity: Severity::RuntimeError,
})
}
DominatedDerive {
opaque,
ability,
derive_region,
impl_region,
} => {
let stack = [
alloc.concat([
alloc.symbol_unqualified(opaque),
alloc.reflow(" both derives and custom-implements "),
alloc.symbol_qualified(ability),
alloc.reflow(". We found the derive here:"),
]),
alloc.region(lines.convert_region(derive_region)),
alloc.concat([
alloc.reflow("and one custom implementation of "),
alloc.symbol_qualified(ability),
alloc.reflow(" here:"),
]),
alloc.region(lines.convert_region(impl_region)),
alloc.concat([
alloc.reflow("Derived and custom implementations can conflict, so one of them needs to be removed!"),
]),
alloc.note("").append(alloc.reflow("We'll try to compile your program using the custom implementation first, and fall-back on the derived implementation if needed. Make sure to disambiguate which one you want!")),
];
Some(Report {
title: "CONFLICTING DERIVE AND IMPLEMENTATION".to_string(),
filename,
doc: alloc.stack(stack),
severity: Severity::Warning,
})
}
}
}
@ -256,31 +289,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 "),
@ -294,6 +308,63 @@ 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 define a custom implementation of "),
alloc.symbol_qualified(ability),
alloc.reflow(" for "),
alloc.symbol_unqualified(opaque),
alloc.reflow("."),
]))));
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};
@ -26,11 +26,13 @@ pub fn test_home() -> ModuleId {
}
#[allow(dead_code)]
#[allow(clippy::too_many_arguments)]
pub fn infer_expr(
subs: Subs,
problems: &mut Vec<solve::TypeError>,
constraints: &Constraints,
constraint: &Constraint,
pending_derives: PendingDerives,
aliases: &mut Aliases,
abilities_store: &mut AbilitiesStore,
expr_var: Variable,
@ -41,6 +43,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,
@ -10168,4 +10172,134 @@ All branches in an `if` must have the same type!
),
)
}
#[test]
fn has_encoding_for_function() {
new_report_problem_as(
"has_encoding_for_function",
indoc!(
r#"
app "test" imports [ Encode ] provides [ A ] to "./platform"
A a := a -> a has [ Encode.Encoding ]
"#
),
indoc!(
r#"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
Roc can't derive an implementation of the `Encode.Encoding` for `A`:
3 A a := a -> a has [ Encode.Encoding ]
^^^^^^^^^^^^^^^
Note: `Encoding` cannot be generated for functions.
Tip: You can define a custom implementation of `Encode.Encoding` for `A`.
"#
),
)
}
#[test]
fn has_encoding_for_non_encoding_alias() {
new_report_problem_as(
"has_encoding_for_non_encoding_alias",
indoc!(
r#"
app "test" imports [ Encode ] provides [ A ] to "./platform"
A := B has [ Encode.Encoding ]
B := {}
"#
),
indoc!(
r#"
INCOMPLETE ABILITY IMPLEMENTATION /code/proj/Main.roc
Roc can't derive an implementation of the `Encode.Encoding` for `A`:
3 A := B has [ Encode.Encoding ]
^^^^^^^^^^^^^^^
Tip: `B` does not implement `Encoding`. Consider adding a custom
implementation or `has Encode.Encoding` to the definition of `B`.
Tip: You can define a custom implementation of `Encode.Encoding` for `A`.
"#
),
)
}
#[test]
fn has_encoding_for_other_has_encoding() {
new_report_problem_as(
"has_encoding_for_other_has_encoding",
indoc!(
r#"
app "test" imports [ Encode ] provides [ A ] to "./platform"
A := B has [ Encode.Encoding ]
B := {} has [ Encode.Encoding ]
"#
),
indoc!(""), // no error
)
}
#[test]
fn has_encoding_for_recursive_deriving() {
new_report_problem_as(
"has_encoding_for_recursive_deriving",
indoc!(
r#"
app "test" imports [ Encode ] provides [ MyNat ] to "./platform"
MyNat := [ S MyNat, Z ] has [ Encode.Encoding ]
"#
),
indoc!(""), // no error
)
}
#[test]
fn has_encoding_dominated_by_custom() {
new_report_problem_as(
"has_encoding_dominated_by_custom",
indoc!(
r#"
app "test" imports [ Encode.{ Encoding, toEncoder, custom } ] provides [ A ] to "./platform"
A := {} has [ Encode.Encoding ]
toEncoder = \@A {} -> custom \l, _ -> l
"#
),
indoc!(
r#"
CONFLICTING DERIVE AND IMPLEMENTATION /code/proj/Main.roc
`A` both derives and custom-implements `Encode.Encoding`. We found the
derive here:
3 A := {} has [ Encode.Encoding ]
^^^^^^^^^^^^^^^
and one custom implementation of `Encode.Encoding` here:
5 toEncoder = \@A {} -> custom \l, _ -> l
^^^^^^^^^
Derived and custom implementations can conflict, so one of them needs
to be removed!
Note: We'll try to compile your program using the custom
implementation first, and fall-back on the derived implementation if
needed. Make sure to disambiguate which one you want!
"#
),
)
}
}