mirror of
https://github.com/roc-lang/roc.git
synced 2025-07-24 06:55:15 +00:00
Eagerly resolve obligations during solving
Replaces the previously-used `DeferredObligations` structure used for accumulating and then acting over ability obligations during module solving in favor of just the `ObligationCache`. The `ObligationCache` stays alive for the entirety of a module solving and provides a convenient mechanism for answering obligation queries with a backed cache.
This commit is contained in:
parent
01eb161ffe
commit
753d12e912
2 changed files with 216 additions and 193 deletions
|
@ -1,6 +1,6 @@
|
|||
use roc_can::abilities::AbilitiesStore;
|
||||
use roc_can::expr::PendingDerives;
|
||||
use roc_collections::VecMap;
|
||||
use roc_collections::{VecMap, VecSet};
|
||||
use roc_error_macros::internal_error;
|
||||
use roc_module::symbol::Symbol;
|
||||
use roc_region::all::{Loc, Region};
|
||||
|
@ -104,159 +104,10 @@ impl PendingDerivesTable {
|
|||
}
|
||||
}
|
||||
|
||||
#[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,
|
||||
}
|
||||
|
||||
impl DeferredObligations {
|
||||
pub fn new(pending_derives: PendingDerivesTable) -> Self {
|
||||
Self {
|
||||
obligations: Default::default(),
|
||||
pending_derives,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add(&mut self, must_implement: MustImplementConstraints, on_error: AbilityImplError) {
|
||||
self.obligations.push((must_implement, on_error));
|
||||
}
|
||||
|
||||
// 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<RequestedDeriveKey>) {
|
||||
let mut problems = vec![];
|
||||
|
||||
let Self {
|
||||
obligations,
|
||||
pending_derives,
|
||||
} = self;
|
||||
|
||||
let mut obligation_cache = ObligationCache {
|
||||
abilities_store,
|
||||
pending_derives: &pending_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())),
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// message.
|
||||
let mut reported_in_context = vec![];
|
||||
let mut incomplete_not_in_context = vec![];
|
||||
|
||||
for (constraints, on_error) in obligations.into_iter() {
|
||||
let must_implement = constraints.get_unique();
|
||||
|
||||
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<_>>()
|
||||
};
|
||||
|
||||
use AbilityImplError::*;
|
||||
match on_error {
|
||||
DoesNotImplement => {
|
||||
// These aren't attached to another type error, so if these must_implement
|
||||
// constraints aren't met, we'll emit a generic "this type doesn't implement an
|
||||
// ability" error message at the end. We only want to do this if it turns out
|
||||
// the "must implement" constraint indeed wasn't part of a more specific type
|
||||
// error.
|
||||
incomplete_not_in_context.extend(must_implement);
|
||||
}
|
||||
BadExpr(region, category, var) => {
|
||||
let unfulfilled = get_unfulfilled(&must_implement);
|
||||
|
||||
if !unfulfilled.is_empty() {
|
||||
// Demote the bad variable that exposed this problem to an error, both so
|
||||
// that we have an ErrorType to report and so that codegen knows to deal
|
||||
// with the error later.
|
||||
let (error_type, _moar_ghosts_n_stuff) = subs.var_to_error_type(var);
|
||||
problems.push(TypeError::BadExprMissingAbility(
|
||||
region,
|
||||
category,
|
||||
error_type,
|
||||
unfulfilled,
|
||||
));
|
||||
reported_in_context.extend(must_implement);
|
||||
}
|
||||
}
|
||||
BadPattern(region, category, var) => {
|
||||
let unfulfilled = get_unfulfilled(&must_implement);
|
||||
|
||||
if !unfulfilled.is_empty() {
|
||||
// Demote the bad variable that exposed this problem to an error, both so
|
||||
// that we have an ErrorType to report and so that codegen knows to deal
|
||||
// with the error later.
|
||||
let (error_type, _moar_ghosts_n_stuff) = subs.var_to_error_type(var);
|
||||
problems.push(TypeError::BadPatternMissingAbility(
|
||||
region,
|
||||
category,
|
||||
error_type,
|
||||
unfulfilled,
|
||||
));
|
||||
reported_in_context.extend(must_implement);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 the obligation is already cached, we must have already reported it in another
|
||||
// context.
|
||||
if !obligation_cache.has_cached(mia) && !reported_in_context.contains(&mia) {
|
||||
if let Err(unfulfilled) = obligation_cache.check_one(subs, mia) {
|
||||
problems.push(TypeError::UnfulfilledAbility(unfulfilled.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(problems, legal_derives)
|
||||
}
|
||||
}
|
||||
|
||||
type ObligationResult = Result<(), Unfulfilled>;
|
||||
|
||||
struct ObligationCache<'a> {
|
||||
abilities_store: &'a AbilitiesStore,
|
||||
pending_derives: &'a PendingDerivesTable,
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ObligationCache {
|
||||
impl_cache: VecMap<ImplKey, ObligationResult>,
|
||||
derive_cache: VecMap<RequestedDeriveKey, ObligationResult>,
|
||||
}
|
||||
|
@ -266,13 +117,144 @@ enum ReadCache {
|
|||
Derive,
|
||||
}
|
||||
|
||||
impl ObligationCache<'_> {
|
||||
fn check_one(&mut self, subs: &mut Subs, mia: MustImplementAbility) -> ObligationResult {
|
||||
pub struct CheckedDerives {
|
||||
pub legal_derives: Vec<RequestedDeriveKey>,
|
||||
pub problems: Vec<TypeError>,
|
||||
}
|
||||
|
||||
impl ObligationCache {
|
||||
#[must_use]
|
||||
pub fn check_derives(
|
||||
&mut self,
|
||||
subs: &mut Subs,
|
||||
abilities_store: &AbilitiesStore,
|
||||
pending_derives: PendingDerivesTable,
|
||||
) -> CheckedDerives {
|
||||
let mut legal_derives = Vec::with_capacity(pending_derives.0.len());
|
||||
let mut problems = vec![];
|
||||
|
||||
// First, check all derives.
|
||||
for (&derive_key, &(opaque_real_var, derive_region)) in pending_derives.0.iter() {
|
||||
self.check_derive(
|
||||
subs,
|
||||
abilities_store,
|
||||
derive_key,
|
||||
opaque_real_var,
|
||||
derive_region,
|
||||
);
|
||||
let result = self.derive_cache.get(&derive_key).unwrap();
|
||||
match result {
|
||||
Ok(()) => legal_derives.push(derive_key),
|
||||
Err(problem) => problems.push(TypeError::UnfulfilledAbility(problem.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
CheckedDerives {
|
||||
legal_derives,
|
||||
problems,
|
||||
}
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn check_obligations(
|
||||
&mut self,
|
||||
subs: &mut Subs,
|
||||
abilities_store: &AbilitiesStore,
|
||||
must_implement: MustImplementConstraints,
|
||||
on_error: AbilityImplError,
|
||||
) -> Vec<TypeError> {
|
||||
let must_implement = must_implement.get_unique();
|
||||
|
||||
let mut get_unfulfilled = |must_implement: &[MustImplementAbility]| {
|
||||
must_implement
|
||||
.iter()
|
||||
.filter_map(|mia| {
|
||||
self.check_one(subs, abilities_store, *mia)
|
||||
.as_ref()
|
||||
.err()
|
||||
.cloned()
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
let mut reported_in_context = VecSet::default();
|
||||
let mut incomplete_not_in_context = VecSet::default();
|
||||
let mut problems = vec![];
|
||||
|
||||
use AbilityImplError::*;
|
||||
match on_error {
|
||||
DoesNotImplement => {
|
||||
// These aren't attached to another type error, so if these must_implement
|
||||
// constraints aren't met, we'll emit a generic "this type doesn't implement an
|
||||
// ability" error message at the end. We only want to do this if it turns out
|
||||
// the "must implement" constraint indeed wasn't part of a more specific type
|
||||
// error.
|
||||
incomplete_not_in_context.extend(must_implement);
|
||||
}
|
||||
BadExpr(region, category, var) => {
|
||||
let unfulfilled = get_unfulfilled(&must_implement);
|
||||
|
||||
if !unfulfilled.is_empty() {
|
||||
// Demote the bad variable that exposed this problem to an error, both so
|
||||
// that we have an ErrorType to report and so that codegen knows to deal
|
||||
// with the error later.
|
||||
let (error_type, _moar_ghosts_n_stuff) = subs.var_to_error_type(var);
|
||||
problems.push(TypeError::BadExprMissingAbility(
|
||||
region,
|
||||
category,
|
||||
error_type,
|
||||
unfulfilled,
|
||||
));
|
||||
reported_in_context.extend(must_implement);
|
||||
}
|
||||
}
|
||||
BadPattern(region, category, var) => {
|
||||
let unfulfilled = get_unfulfilled(&must_implement);
|
||||
|
||||
if !unfulfilled.is_empty() {
|
||||
// Demote the bad variable that exposed this problem to an error, both so
|
||||
// that we have an ErrorType to report and so that codegen knows to deal
|
||||
// with the error later.
|
||||
let (error_type, _moar_ghosts_n_stuff) = subs.var_to_error_type(var);
|
||||
problems.push(TypeError::BadPatternMissingAbility(
|
||||
region,
|
||||
category,
|
||||
error_type,
|
||||
unfulfilled,
|
||||
));
|
||||
reported_in_context.extend(must_implement);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 the obligation is already cached, we must have already reported it in another
|
||||
// context.
|
||||
if !self.has_cached(mia) && !reported_in_context.contains(&mia) {
|
||||
if let Err(unfulfilled) = self.check_one(subs, abilities_store, mia) {
|
||||
problems.push(TypeError::UnfulfilledAbility(unfulfilled.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
problems
|
||||
}
|
||||
|
||||
fn check_one(
|
||||
&mut self,
|
||||
subs: &mut Subs,
|
||||
abilities_store: &AbilitiesStore,
|
||||
mia: MustImplementAbility,
|
||||
) -> ObligationResult {
|
||||
let MustImplementAbility { typ, ability } = mia;
|
||||
|
||||
match typ {
|
||||
Obligated::Adhoc(var) => self.check_adhoc(subs, var, ability),
|
||||
Obligated::Opaque(opaque) => self.check_opaque_and_read(subs, opaque, ability).clone(),
|
||||
Obligated::Adhoc(var) => self.check_adhoc(subs, abilities_store, var, ability),
|
||||
Obligated::Opaque(opaque) => self
|
||||
.check_opaque_and_read(subs, abilities_store, opaque, ability)
|
||||
.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -289,12 +271,18 @@ impl ObligationCache<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_adhoc(&mut self, subs: &mut Subs, var: Variable, ability: Symbol) -> ObligationResult {
|
||||
fn check_adhoc(
|
||||
&mut self,
|
||||
subs: &mut Subs,
|
||||
abilities_store: &AbilitiesStore,
|
||||
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)),
|
||||
Symbol::ENCODE_ENCODING => Some(self.can_derive_encoding(subs, abilities_store, var)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
|
@ -325,30 +313,28 @@ impl ObligationCache<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_opaque(&mut self, subs: &mut Subs, opaque: Symbol, ability: Symbol) -> ReadCache {
|
||||
fn check_opaque(
|
||||
&mut self,
|
||||
subs: &mut Subs,
|
||||
abilities_store: &AbilitiesStore,
|
||||
opaque: Symbol,
|
||||
ability: Symbol,
|
||||
) -> ReadCache {
|
||||
let impl_key = ImplKey { opaque, ability };
|
||||
let derive_key = RequestedDeriveKey { opaque, ability };
|
||||
|
||||
match self.pending_derives.0.get(&derive_key) {
|
||||
Some(&(opaque_real_var, derive_region)) => {
|
||||
self.check_derive(subs, derive_key, opaque_real_var, derive_region);
|
||||
ReadCache::Derive
|
||||
}
|
||||
// Only an impl
|
||||
None => {
|
||||
self.check_impl(impl_key);
|
||||
ReadCache::Impl
|
||||
}
|
||||
}
|
||||
self.check_impl(abilities_store, impl_key);
|
||||
ReadCache::Impl
|
||||
}
|
||||
|
||||
fn check_opaque_and_read(
|
||||
&mut self,
|
||||
subs: &mut Subs,
|
||||
abilities_store: &AbilitiesStore,
|
||||
opaque: Symbol,
|
||||
ability: Symbol,
|
||||
) -> &ObligationResult {
|
||||
match self.check_opaque(subs, opaque, ability) {
|
||||
match self.check_opaque(subs, abilities_store, opaque, ability) {
|
||||
ReadCache::Impl => self.impl_cache.get(&ImplKey { opaque, ability }).unwrap(),
|
||||
ReadCache::Derive => self
|
||||
.derive_cache
|
||||
|
@ -357,15 +343,13 @@ impl ObligationCache<'_> {
|
|||
}
|
||||
}
|
||||
|
||||
fn check_impl(&mut self, impl_key: ImplKey) {
|
||||
fn check_impl(&mut self, abilities_store: &AbilitiesStore, impl_key: ImplKey) {
|
||||
if self.impl_cache.get(&impl_key).is_some() {
|
||||
return;
|
||||
}
|
||||
|
||||
let ImplKey { opaque, ability } = impl_key;
|
||||
let has_declared_impl = self
|
||||
.abilities_store
|
||||
.has_declared_implementation(opaque, ability);
|
||||
let has_declared_impl = abilities_store.has_declared_implementation(opaque, ability);
|
||||
|
||||
let obligation_result = if !has_declared_impl {
|
||||
Err(Unfulfilled::OpaqueDoesNotImplement {
|
||||
|
@ -382,6 +366,7 @@ impl ObligationCache<'_> {
|
|||
fn check_derive(
|
||||
&mut self,
|
||||
subs: &mut Subs,
|
||||
abilities_store: &AbilitiesStore,
|
||||
derive_key: RequestedDeriveKey,
|
||||
opaque_real_var: Variable,
|
||||
derive_region: Region,
|
||||
|
@ -410,7 +395,8 @@ impl ObligationCache<'_> {
|
|||
|
||||
// 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 real_var_result =
|
||||
self.check_adhoc(subs, abilities_store, 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
|
||||
|
@ -445,7 +431,12 @@ impl ObligationCache<'_> {
|
|||
// If we have a lot of these, consider using a visitor.
|
||||
// It will be very similar for most types (can't derive functions, can't derive unbound type
|
||||
// variables, can only derive opaques if they have an impl, etc).
|
||||
fn can_derive_encoding(&mut self, subs: &mut Subs, var: Variable) -> Result<(), Variable> {
|
||||
fn can_derive_encoding(
|
||||
&mut self,
|
||||
subs: &mut Subs,
|
||||
abilities_store: &AbilitiesStore,
|
||||
var: Variable,
|
||||
) -> Result<(), Variable> {
|
||||
let mut stack = vec![var];
|
||||
let mut seen_recursion_vars = vec![];
|
||||
|
||||
|
@ -543,7 +534,12 @@ impl ObligationCache<'_> {
|
|||
Alias(name, _, _, AliasKind::Opaque) => {
|
||||
let opaque = *name;
|
||||
if self
|
||||
.check_opaque_and_read(subs, opaque, Symbol::ENCODE_ENCODING)
|
||||
.check_opaque_and_read(
|
||||
subs,
|
||||
abilities_store,
|
||||
opaque,
|
||||
Symbol::ENCODE_ENCODING,
|
||||
)
|
||||
.is_err()
|
||||
{
|
||||
return Err(var);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::ability::{
|
||||
resolve_ability_specialization, type_implementing_specialization, AbilityImplError,
|
||||
DeferredObligations, PendingDerivesTable, Resolved, Unfulfilled,
|
||||
CheckedDerives, ObligationCache, PendingDerivesTable, Resolved, Unfulfilled,
|
||||
};
|
||||
use crate::module::Solved;
|
||||
use bumpalo::Bump;
|
||||
|
@ -674,8 +674,14 @@ fn run_in_place(
|
|||
let rank = Rank::toplevel();
|
||||
let arena = Bump::new();
|
||||
|
||||
let mut obligation_cache = ObligationCache::default();
|
||||
|
||||
let pending_derives = PendingDerivesTable::new(subs, aliases, pending_derives);
|
||||
let mut deferred_obligations = DeferredObligations::new(pending_derives);
|
||||
let CheckedDerives {
|
||||
legal_derives: _,
|
||||
problems: derives_problems,
|
||||
} = obligation_cache.check_derives(subs, abilities_store, pending_derives);
|
||||
problems.extend(derives_problems);
|
||||
|
||||
// Because we don't know what ability specializations are available until the entire module is
|
||||
// solved, we must wait to solve unspecialized lambda sets then.
|
||||
|
@ -692,7 +698,7 @@ fn run_in_place(
|
|||
subs,
|
||||
constraint,
|
||||
abilities_store,
|
||||
&mut deferred_obligations,
|
||||
&mut obligation_cache,
|
||||
&mut deferred_uls_to_resolve,
|
||||
);
|
||||
|
||||
|
@ -708,11 +714,12 @@ fn run_in_place(
|
|||
&SolvePhase { abilities_store },
|
||||
exposed_by_module,
|
||||
);
|
||||
|
||||
deferred_obligations.add(new_must_implement, AbilityImplError::DoesNotImplement);
|
||||
|
||||
let (obligation_problems, _derived) = deferred_obligations.check_all(subs, abilities_store);
|
||||
problems.extend(obligation_problems);
|
||||
problems.extend(obligation_cache.check_obligations(
|
||||
subs,
|
||||
abilities_store,
|
||||
new_must_implement,
|
||||
AbilityImplError::DoesNotImplement,
|
||||
));
|
||||
|
||||
state.env
|
||||
}
|
||||
|
@ -765,7 +772,7 @@ fn solve(
|
|||
subs: &mut Subs,
|
||||
constraint: &Constraint,
|
||||
abilities_store: &mut AbilitiesStore,
|
||||
deferred_obligations: &mut DeferredObligations,
|
||||
obligation_cache: &mut ObligationCache,
|
||||
deferred_uls_to_resolve: &mut UlsOfVar,
|
||||
) -> State {
|
||||
let initial = Work::Constraint {
|
||||
|
@ -989,11 +996,15 @@ fn solve(
|
|||
extra_metadata: _,
|
||||
} => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
if !must_implement_ability.is_empty() {
|
||||
deferred_obligations.add(
|
||||
let new_problems = obligation_cache.check_obligations(
|
||||
subs,
|
||||
abilities_store,
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadExpr(*region, category.clone(), actual),
|
||||
);
|
||||
problems.extend(new_problems);
|
||||
}
|
||||
deferred_uls_to_resolve.union(lambda_sets_to_specialize);
|
||||
|
||||
|
@ -1103,8 +1114,11 @@ fn solve(
|
|||
extra_metadata: _,
|
||||
} => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
if !must_implement_ability.is_empty() {
|
||||
deferred_obligations.add(
|
||||
let new_problems = obligation_cache.check_obligations(
|
||||
subs,
|
||||
abilities_store,
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadExpr(
|
||||
*region,
|
||||
|
@ -1112,6 +1126,7 @@ fn solve(
|
|||
actual,
|
||||
),
|
||||
);
|
||||
problems.extend(new_problems);
|
||||
}
|
||||
deferred_uls_to_resolve.union(lambda_sets_to_specialize);
|
||||
|
||||
|
@ -1183,11 +1198,15 @@ fn solve(
|
|||
extra_metadata: _,
|
||||
} => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
if !must_implement_ability.is_empty() {
|
||||
deferred_obligations.add(
|
||||
let new_problems = obligation_cache.check_obligations(
|
||||
subs,
|
||||
abilities_store,
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadPattern(*region, category.clone(), actual),
|
||||
);
|
||||
problems.extend(new_problems);
|
||||
}
|
||||
deferred_uls_to_resolve.union(lambda_sets_to_specialize);
|
||||
|
||||
|
@ -1348,8 +1367,11 @@ fn solve(
|
|||
extra_metadata: _,
|
||||
} => {
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
if !must_implement_ability.is_empty() {
|
||||
deferred_obligations.add(
|
||||
let new_problems = obligation_cache.check_obligations(
|
||||
subs,
|
||||
abilities_store,
|
||||
must_implement_ability,
|
||||
AbilityImplError::BadPattern(
|
||||
*region,
|
||||
|
@ -1357,6 +1379,7 @@ fn solve(
|
|||
actual,
|
||||
),
|
||||
);
|
||||
problems.extend(new_problems);
|
||||
}
|
||||
deferred_uls_to_resolve.union(lambda_sets_to_specialize);
|
||||
|
||||
|
@ -1458,8 +1481,12 @@ fn solve(
|
|||
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
||||
deferred_obligations
|
||||
.add(must_implement_ability, AbilityImplError::DoesNotImplement);
|
||||
problems.extend(obligation_cache.check_obligations(
|
||||
subs,
|
||||
abilities_store,
|
||||
must_implement_ability,
|
||||
AbilityImplError::DoesNotImplement,
|
||||
));
|
||||
deferred_uls_to_resolve.union(lambda_sets_to_specialize);
|
||||
|
||||
// Case 1: unify error types, but don't check exhaustiveness.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue