From 0ec92c12f7acebb3cc9d683e1a4d03c92b18f717 Mon Sep 17 00:00:00 2001 From: Ayaz Hafiz Date: Tue, 26 Jul 2022 09:23:40 -0400 Subject: [PATCH] Move lambda set specialization to its own module in solve --- crates/compiler/late_solve/src/lib.rs | 3 +- crates/compiler/solve/src/lib.rs | 1 + crates/compiler/solve/src/solve.rs | 645 +----------------------- crates/compiler/solve/src/specialize.rs | 641 +++++++++++++++++++++++ 4 files changed, 656 insertions(+), 634 deletions(-) create mode 100644 crates/compiler/solve/src/specialize.rs diff --git a/crates/compiler/late_solve/src/lib.rs b/crates/compiler/late_solve/src/lib.rs index 34b3467dbc..7a00ec54bf 100644 --- a/crates/compiler/late_solve/src/lib.rs +++ b/crates/compiler/late_solve/src/lib.rs @@ -10,7 +10,8 @@ use roc_collections::MutMap; use roc_derive::SharedDerivedModule; use roc_error_macros::internal_error; use roc_module::symbol::ModuleId; -use roc_solve::solve::{compact_lambda_sets_of_vars, Phase, Pools}; +use roc_solve::solve::Pools; +use roc_solve::specialize::{compact_lambda_sets_of_vars, Phase}; use roc_types::subs::{get_member_lambda_sets_at_region, Content, FlatType, LambdaSet}; use roc_types::subs::{ExposedTypesStorageSubs, Subs, Variable}; use roc_unify::unify::{unify as unify_unify, Env, Mode, Unified}; diff --git a/crates/compiler/solve/src/lib.rs b/crates/compiler/solve/src/lib.rs index d0e42eaf42..8f6a0bcafb 100644 --- a/crates/compiler/solve/src/lib.rs +++ b/crates/compiler/solve/src/lib.rs @@ -5,3 +5,4 @@ pub mod ability; pub mod module; pub mod solve; +pub mod specialize; diff --git a/crates/compiler/solve/src/solve.rs b/crates/compiler/solve/src/solve.rs index c4b926ddcb..6c344fe1ca 100644 --- a/crates/compiler/solve/src/solve.rs +++ b/crates/compiler/solve/src/solve.rs @@ -3,6 +3,7 @@ use crate::ability::{ CheckedDerives, ObligationCache, PendingDerivesTable, Resolved, }; use crate::module::Solved; +use crate::specialize::{compact_lambda_sets_of_vars, SolvePhase}; use bumpalo::Bump; use roc_can::abilities::{AbilitiesStore, MemberSpecializationInfo}; use roc_can::constraint::Constraint::{self, *}; @@ -13,28 +14,27 @@ use roc_can::module::ExposedByModule; use roc_collections::all::MutMap; use roc_debug_flags::dbg_do; #[cfg(debug_assertions)] -use roc_debug_flags::{ROC_TRACE_COMPACTION, ROC_VERIFY_RIGID_LET_GENERALIZED}; +use roc_debug_flags::ROC_VERIFY_RIGID_LET_GENERALIZED; use roc_derive::SharedDerivedModule; -use roc_derive_key::{DeriveError, DeriveKey}; -use roc_error_macros::{internal_error, todo_abilities}; +use roc_error_macros::internal_error; use roc_module::ident::TagName; use roc_module::symbol::{ModuleId, Symbol}; use roc_problem::can::CycleEntry; use roc_region::all::{Loc, Region}; use roc_solve_problem::TypeError; use roc_types::subs::{ - self, get_member_lambda_sets_at_region, AliasVariables, Content, Descriptor, FlatType, - GetSubsSlice, LambdaSet, Mark, OptVariable, Rank, RecordFields, Subs, SubsIndex, SubsSlice, - UlsOfVar, UnionLabels, UnionLambdas, UnionTags, Variable, VariableSubsSlice, + self, AliasVariables, Content, Descriptor, FlatType, GetSubsSlice, LambdaSet, Mark, + OptVariable, Rank, RecordFields, Subs, SubsIndex, SubsSlice, UlsOfVar, UnionLabels, + UnionLambdas, UnionTags, Variable, VariableSubsSlice, }; use roc_types::types::Type::{self, *}; use roc_types::types::{ - gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, MemberImpl, OptAbleType, - OptAbleVar, Reason, TypeExtension, Uls, + gather_fields_unsorted_iter, AliasCommon, AliasKind, Category, OptAbleType, OptAbleVar, Reason, + TypeExtension, Uls, }; use roc_unify::unify::{ - unify, unify_introduced_ability_specialization, Env as UEnv, Mode, MustImplementConstraints, - Obligated, SpecializationLsetCollector, Unified::*, + unify, unify_introduced_ability_specialization, Env as UEnv, Mode, Obligated, + SpecializationLsetCollector, Unified::*, }; // Type checking system adapted from Elm by Evan Czaplicki, BSD-3-Clause Licensed @@ -499,98 +499,6 @@ impl Pools { } } -/// What phase in the compiler is reaching out to solve types. -/// This is important to distinguish subtle differences in the behavior of the solving algorithm. -// -// TODO the APIs of this trait suck, this needs a nice cleanup. -pub trait Phase { - /// The regular type-solving phase, or during some later phase of compilation. - /// During the solving phase we must anticipate that some information is still unknown and react to - /// that; during late phases, we expect that all information is resolved. - const IS_LATE: bool; - - fn with_module_abilities_store(&self, module: ModuleId, f: F) -> T - where - F: FnMut(&AbilitiesStore) -> T; - - /// Given a known lambda set's ambient function in an external module, copy that ambient - /// function into the given subs. - fn copy_lambda_set_ambient_function_to_home_subs( - &self, - external_lambda_set_var: Variable, - external_module_id: ModuleId, - home_subs: &mut Subs, - ) -> Variable; - - /// Find the ambient function var at a given region for an ability member definition (not a - /// specialization!), and copy that into the given subs. - fn get_and_copy_ability_member_ambient_function( - &self, - ability_member: Symbol, - region: u8, - home_subs: &mut Subs, - ) -> Variable; -} - -struct SolvePhase<'a> { - abilities_store: &'a AbilitiesStore, -} -impl Phase for SolvePhase<'_> { - const IS_LATE: bool = false; - - fn with_module_abilities_store(&self, _module: ModuleId, mut f: F) -> T - where - F: FnMut(&AbilitiesStore) -> T, - { - // During solving we're only aware of our module's abilities store. - f(self.abilities_store) - } - - fn copy_lambda_set_ambient_function_to_home_subs( - &self, - external_lambda_set_var: Variable, - _external_module_id: ModuleId, - home_subs: &mut Subs, - ) -> Variable { - // During solving we're only aware of our module's abilities store, the var must - // be in our module store. Even if the specialization lambda set comes from another - // module, we should have taken care to import it before starting solving in this module. - let LambdaSet { - ambient_function, .. - } = home_subs.get_lambda_set(external_lambda_set_var); - ambient_function - } - - fn get_and_copy_ability_member_ambient_function( - &self, - ability_member: Symbol, - region: u8, - home_subs: &mut Subs, - ) -> Variable { - // During solving we're only aware of our module's abilities store, the var must - // be in our module store. Even if the specialization lambda set comes from another - // module, we should have taken care to import it before starting solving in this module. - let member_def = self - .abilities_store - .member_def(ability_member) - .unwrap_or_else(|| { - internal_error!( - "{:?} is not resolved, or not an ability member!", - ability_member - ) - }); - let member_var = member_def.signature_var(); - - let region_lset = get_member_lambda_sets_at_region(home_subs, member_var, region); - - let LambdaSet { - ambient_function, .. - } = home_subs.get_lambda_set(region_lset); - - ambient_function - } -} - #[derive(Clone)] struct State { env: Env, @@ -1868,534 +1776,6 @@ fn check_ability_specialization( } } -#[cfg(debug_assertions)] -fn trace_compaction_step_1(subs: &Subs, c_a: Variable, uls_a: &[Variable]) { - let c_a = roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(c_a), subs); - let uls_a = uls_a - .iter() - .map(|v| { - format!( - "{:?}", - roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(*v), subs) - ) - }) - .collect::>() - .join(","); - eprintln!("===lambda set compaction==="); - eprintln!(" concrete type: {:?}", c_a); - eprintln!(" step 1:"); - eprintln!(" uls_a = {{ {} }}", uls_a); -} - -#[cfg(debug_assertions)] -fn trace_compaction_step_2(subs: &Subs, uls_a: &[Variable]) { - let uls_a = uls_a - .iter() - .map(|v| { - format!( - "{:?}", - roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(*v), subs) - ) - }) - .collect::>() - .join(","); - eprintln!(" step 2:"); - eprintln!(" uls_a' = {{ {} }}", uls_a); -} - -#[cfg(debug_assertions)] -fn trace_compaction_step_3start() { - eprintln!(" step 3:"); -} - -#[cfg(debug_assertions)] -fn trace_compaction_step_3iter_start( - subs: &Subs, - iteration_lambda_set: Variable, - t_f1: Variable, - t_f2: Variable, -) { - let iteration_lambda_set = roc_types::subs::SubsFmtContent( - subs.get_content_without_compacting(iteration_lambda_set), - subs, - ); - let t_f1 = roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(t_f1), subs); - let t_f2 = roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(t_f2), subs); - eprintln!(" - iteration: {:?}", iteration_lambda_set); - eprintln!(" {:?}", t_f1); - eprintln!(" ~ {:?}", t_f2); -} - -#[cfg(debug_assertions)] -#[rustfmt::skip] -fn trace_compaction_step_3iter_end(subs: &Subs, t_f_result: Variable, skipped: bool) { - let t_f_result = - roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(t_f_result), subs); - if skipped { - eprintln!(" SKIP"); - } - eprintln!(" = {:?}\n", t_f_result); -} - -macro_rules! trace_compact { - (1. $subs:expr, $c_a:expr, $uls_a:expr) => {{ - dbg_do!(ROC_TRACE_COMPACTION, { - trace_compaction_step_1($subs, $c_a, $uls_a) - }) - }}; - (2. $subs:expr, $uls_a:expr) => {{ - dbg_do!(ROC_TRACE_COMPACTION, { - trace_compaction_step_2($subs, $uls_a) - }) - }}; - (3start.) => {{ - dbg_do!(ROC_TRACE_COMPACTION, { trace_compaction_step_3start() }) - }}; - (3iter_start. $subs:expr, $iteration_lset:expr, $t_f1:expr, $t_f2:expr) => {{ - dbg_do!(ROC_TRACE_COMPACTION, { - trace_compaction_step_3iter_start($subs, $iteration_lset, $t_f1, $t_f2) - }) - }}; - (3iter_end. $subs:expr, $t_f_result:expr) => {{ - dbg_do!(ROC_TRACE_COMPACTION, { - trace_compaction_step_3iter_end($subs, $t_f_result, false) - }) - }}; - (3iter_end_skipped. $subs:expr, $t_f_result:expr) => {{ - dbg_do!(ROC_TRACE_COMPACTION, { - trace_compaction_step_3iter_end($subs, $t_f_result, true) - }) - }}; -} - -#[inline(always)] -fn iter_concrete_of_unspecialized<'a>( - subs: &'a Subs, - c_a: Variable, - uls: &'a [Uls], -) -> impl Iterator { - uls.iter() - .filter(move |Uls(var, _, _)| subs.equivalent_without_compacting(*var, c_a)) -} - -/// Gets the unique unspecialized lambda resolving to concrete type `c_a` in a list of -/// unspecialized lambda sets. -#[inline(always)] -fn unique_unspecialized_lambda(subs: &Subs, c_a: Variable, uls: &[Uls]) -> Option { - let mut iter_concrete = iter_concrete_of_unspecialized(subs, c_a, uls); - let uls = iter_concrete.next()?; - debug_assert!(iter_concrete.next().is_none(), "multiple concrete"); - Some(*uls) -} - -#[must_use] -pub fn compact_lambda_sets_of_vars( - subs: &mut Subs, - derived_module: &SharedDerivedModule, - arena: &Bump, - pools: &mut Pools, - uls_of_var: UlsOfVar, - phase: &P, - exposed_by_module: &ExposedByModule, -) -> MustImplementConstraints { - // let mut seen = VecSet::default(); - let mut must_implement = MustImplementConstraints::default(); - - let mut uls_of_var_queue = VecDeque::with_capacity(uls_of_var.len()); - uls_of_var_queue.extend(uls_of_var.drain()); - - // Suppose a type variable `a` with `uls_of_var` mapping `uls_a = {l1, ... ln}` has been instantiated to a concrete type `C_a`. - while let Some((c_a, uls_a)) = uls_of_var_queue.pop_front() { - let c_a = subs.get_root_key_without_compacting(c_a); - // 1. Let each `l` in `uls_a` be of form `[solved_lambdas + ... + C:f:r + ...]`. - // NB: There may be multiple unspecialized lambdas of form `C:f:r, C:f1:r1, ..., C:fn:rn` in `l`. - // In this case, let `t1, ... tm` be the other unspecialized lambdas not of form `C:_:_`, - // that is, none of which are now specialized to the type `C`. Then, deconstruct - // `l` such that `l' = [solved_lambdas + t1 + ... + tm + C:f:r]` and `l1 = [[] + C:f1:r1], ..., ln = [[] + C:fn:rn]`. - // Replace `l` with `l', l1, ..., ln` in `uls_a`, flattened. - // TODO: the flattening step described above - let uls_a = uls_a.into_vec(); - trace_compact!(1. subs, c_a, &uls_a); - - // The flattening step - remove lambda sets that don't reference the concrete var, and for - // flatten lambda sets that reference it more than once. - let mut uls_a: Vec<_> = uls_a - .into_iter() - .flat_map(|lambda_set| { - let LambdaSet { - solved, - recursion_var, - unspecialized, - ambient_function, - } = subs.get_lambda_set(lambda_set); - let lambda_set_rank = subs.get_rank(lambda_set); - let unspecialized = subs.get_subs_slice(unspecialized); - // TODO: is it faster to traverse once, see if we only have one concrete lambda, and - // bail in that happy-path, rather than always splitting? - let (concrete, mut not_concrete): (Vec<_>, Vec<_>) = unspecialized - .iter() - .copied() - .partition(|Uls(var, _, _)| subs.equivalent_without_compacting(*var, c_a)); - if concrete.len() == 1 { - // No flattening needs to be done, just return the lambda set as-is - return vec![lambda_set]; - } - // Must flatten - concrete - .into_iter() - .enumerate() - .map(|(i, concrete_lambda)| { - let (var, unspecialized) = if i == 0 { - // The first lambda set contains one concrete lambda, plus all solved - // lambdas, plus all other unspecialized lambdas. - // l' = [solved_lambdas + t1 + ... + tm + C:f:r] - let unspecialized = SubsSlice::extend_new( - &mut subs.unspecialized_lambda_sets, - not_concrete - .drain(..) - .chain(std::iter::once(concrete_lambda)), - ); - (lambda_set, unspecialized) - } else { - // All the other lambda sets consists only of their respective concrete - // lambdas. - // ln = [[] + C:fn:rn] - let unspecialized = SubsSlice::extend_new( - &mut subs.unspecialized_lambda_sets, - [concrete_lambda], - ); - let var = subs.fresh(Descriptor { - content: Content::Error, - rank: lambda_set_rank, - mark: Mark::NONE, - copy: OptVariable::NONE, - }); - (var, unspecialized) - }; - - subs.set_content( - var, - Content::LambdaSet(LambdaSet { - solved, - recursion_var, - unspecialized, - ambient_function, - }), - ); - var - }) - .collect() - }) - .collect(); - - // 2. Now, each `l` in `uls_a` has a unique unspecialized lambda of form `C:f:r`. - // Sort `uls_a` primarily by `f` (arbitrary order), and secondarily by `r` in descending order. - uls_a.sort_by(|v1, v2| { - let unspec_1 = subs.get_subs_slice(subs.get_lambda_set(*v1).unspecialized); - let unspec_2 = subs.get_subs_slice(subs.get_lambda_set(*v2).unspecialized); - - let Uls(_, f1, r1) = unique_unspecialized_lambda(subs, c_a, unspec_1).unwrap(); - let Uls(_, f2, r2) = unique_unspecialized_lambda(subs, c_a, unspec_2).unwrap(); - - match f1.cmp(&f2) { - std::cmp::Ordering::Equal => { - // Order by descending order of region. - r2.cmp(&r1) - } - ord => ord, - } - }); - - trace_compact!(2. subs, &uls_a); - - // 3. For each `l` in `uls_a` with unique unspecialized lambda `C:f:r`: - // 1. Let `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. Remove `C:f:r` from `t_f1`'s lambda set. - // - For example, `(b' -[[] + Fo:f:2]-> {})` if `C:f:r=Fo:f:2`. Removing `Fo:f:2`, we get `(b' -[[]]-> {})`. - // 2. Let `t_f2` be the directly ambient function of the specialization lambda set resolved by `C:f:r`. - // - For example, `(b -[[] + b:g:1]-> {})` if `C:f:r=Fo:f:2`, running on example from above. - // 3. Unify `t_f1 ~ t_f2`. - trace_compact!(3start.); - for l in uls_a { - // let root_lset = subs.get_root_key_without_compacting(l); - // if seen.contains(&root_lset) { - // continue; - // } - - let (new_must_implement, new_uls_of_var) = compact_lambda_set( - subs, - derived_module, - arena, - pools, - c_a, - l, - phase, - exposed_by_module, - ); - - must_implement.extend(new_must_implement); - uls_of_var_queue.extend(new_uls_of_var.drain()); - - // seen.insert(root_lset); - } - } - - must_implement -} - -#[must_use] -#[allow(clippy::too_many_arguments)] -fn compact_lambda_set( - subs: &mut Subs, - derived_module: &SharedDerivedModule, - arena: &Bump, - pools: &mut Pools, - resolved_concrete: Variable, - this_lambda_set: Variable, - phase: &P, - exposed_by_module: &ExposedByModule, -) -> (MustImplementConstraints, UlsOfVar) { - // 3. For each `l` in `uls_a` with unique unspecialized lambda `C:f:r`: - // 1. Let `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. Remove `C:f:r` from `t_f1`'s lambda set. - // - For example, `(b' -[[] + Fo:f:2]-> {})` if `C:f:r=Fo:f:2`. Removing `Fo:f:2`, we get `(b' -[[]]-> {})`. - // 2. Let `t_f2` be the directly ambient function of the specialization lambda set resolved by `C:f:r`. - // - For example, `(b -[[] + b:g:1]-> {})` if `C:f:r=Fo:f:2`, from the algorithm's running example. - // 3. Unify `t_f1 ~ t_f2`. - let LambdaSet { - solved, - recursion_var, - unspecialized, - ambient_function: t_f1, - } = subs.get_lambda_set(this_lambda_set); - let target_rank = subs.get_rank(this_lambda_set); - - debug_assert!(!unspecialized.is_empty()); - - let unspecialized = subs.get_subs_slice(unspecialized); - - // 1. Let `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. - let Uls(c, f, r) = unique_unspecialized_lambda(subs, resolved_concrete, unspecialized).unwrap(); - - debug_assert!(subs.equivalent_without_compacting(c, resolved_concrete)); - - // 1b. Remove `C:f:r` from `t_f1`'s lambda set. - let new_unspecialized: Vec<_> = unspecialized - .iter() - .filter(|Uls(v, _, _)| !subs.equivalent_without_compacting(*v, resolved_concrete)) - .copied() - .collect(); - debug_assert_eq!(new_unspecialized.len(), unspecialized.len() - 1); - let t_f1_lambda_set_without_concrete = LambdaSet { - solved, - recursion_var, - unspecialized: SubsSlice::extend_new( - &mut subs.unspecialized_lambda_sets, - new_unspecialized, - ), - ambient_function: t_f1, - }; - subs.set_content( - this_lambda_set, - Content::LambdaSet(t_f1_lambda_set_without_concrete), - ); - - let specialization_decision = make_specialization_decision(subs, c); - - let specialization_key = match specialization_decision { - SpecializeDecision::Specialize(key) => key, - SpecializeDecision::Drop => { - // Do nothing other than to remove the concrete lambda to drop from the lambda set, - // which we already did in 1b above. - trace_compact!(3iter_end_skipped. subs, t_f1); - return (Default::default(), Default::default()); - } - }; - - let specialization_ambient_function_var = get_specialization_lambda_set_ambient_function( - subs, - derived_module, - phase, - f, - r, - specialization_key, - exposed_by_module, - target_rank, - ); - - let t_f2 = match specialization_ambient_function_var { - Ok(lset) => lset, - Err(()) => { - // Do nothing other than to remove the concrete lambda to drop from the lambda set, - // which we already did in 1b above. - trace_compact!(3iter_end_skipped. subs, t_f1); - return (Default::default(), Default::default()); - } - }; - - // Ensure the specialized ambient function we'll unify with is not a generalized one, but one - // at the rank of the lambda set being compacted. - let t_f2 = deep_copy_var_in(subs, target_rank, pools, t_f2, arena); - - // 3. Unify `t_f1 ~ t_f2`. - trace_compact!(3iter_start. subs, this_lambda_set, t_f1, t_f2); - let (vars, new_must_implement_ability, new_lambda_sets_to_specialize, _meta) = - unify(&mut UEnv::new(subs), t_f1, t_f2, Mode::EQ) - .expect_success("ambient functions don't unify"); - trace_compact!(3iter_end. subs, t_f1); - - introduce(subs, target_rank, pools, &vars); - - (new_must_implement_ability, new_lambda_sets_to_specialize) -} - -enum SpecializationTypeKey { - Opaque(Symbol), - Derived(DeriveKey), - Immediate(Symbol), -} - -enum SpecializeDecision { - Specialize(SpecializationTypeKey), - Drop, -} - -fn make_specialization_decision(subs: &Subs, var: Variable) -> SpecializeDecision { - use Content::*; - use SpecializationTypeKey::*; - match subs.get_content_without_compacting(var) { - Alias(opaque, _, _, AliasKind::Opaque) if opaque.module_id() != ModuleId::NUM => { - SpecializeDecision::Specialize(Opaque(*opaque)) - } - Structure(_) | Alias(_, _, _, _) => { - // This is a structural type, find the name of the derived ability function it - // should use. - match roc_derive_key::Derived::encoding(subs, var) { - Ok(derived) => match derived { - roc_derive_key::Derived::Immediate(imm) => { - SpecializeDecision::Specialize(Immediate(imm)) - // todo!("deal with lambda set extraction from immediates") - } - roc_derive_key::Derived::Key(derive_key) => { - SpecializeDecision::Specialize(Derived(derive_key)) - } - }, - Err(DeriveError::UnboundVar) => { - // not specialized yet, but that also means that it can't possibly be derivable - // at this point? - // TODO: is this right? Revisit if it causes us problems in the future. - SpecializeDecision::Drop - } - Err(DeriveError::Underivable) => { - // we should have reported an error for this; drop the lambda set. - SpecializeDecision::Drop - } - } - } - Error => SpecializeDecision::Drop, - FlexAbleVar(_, _) - | RigidAbleVar(..) - | FlexVar(..) - | RigidVar(..) - | RecursionVar { .. } - | LambdaSet(..) - | RangedNumber(..) => { - internal_error!("unexpected") - } - } -} - -#[allow(clippy::too_many_arguments)] -fn get_specialization_lambda_set_ambient_function( - subs: &mut Subs, - derived_module: &SharedDerivedModule, - phase: &P, - ability_member: Symbol, - lset_region: u8, - specialization_key: SpecializationTypeKey, - exposed_by_module: &ExposedByModule, - target_rank: Rank, -) -> Result { - match specialization_key { - SpecializationTypeKey::Opaque(opaque) => { - let opaque_home = opaque.module_id(); - let external_specialized_lset = - phase.with_module_abilities_store(opaque_home, |abilities_store| { - let impl_key = roc_can::abilities::ImplKey { - opaque, - ability_member, - }; - let opt_specialization = - abilities_store.get_implementation(impl_key); - match (P::IS_LATE, opt_specialization) { - (false, None) => { - // doesn't specialize, we'll have reported an error for this - Err(()) - } - (true, None) => { - internal_error!( - "expected to know a specialization for {:?}#{:?}, but it wasn't found", - opaque, - ability_member, - ); - } - (_, Some(member_impl)) => match member_impl { - MemberImpl::Impl(spec_symbol) => { - let specialization = - abilities_store.specialization_info(*spec_symbol).expect("expected custom implementations to always have complete specialization info by this point"); - - let specialized_lambda_set = *specialization - .specialization_lambda_sets - .get(&lset_region) - .expect("lambda set region not resolved"); - Ok(specialized_lambda_set) - } - MemberImpl::Derived => todo_abilities!(), - MemberImpl::Error => todo_abilities!(), - }, - } - })?; - - let specialized_ambient = phase.copy_lambda_set_ambient_function_to_home_subs( - external_specialized_lset, - opaque_home, - subs, - ); - - Ok(specialized_ambient) - } - - SpecializationTypeKey::Derived(derive_key) => { - let mut derived_module = derived_module.lock().unwrap(); - - let (_, _, specialization_lambda_sets) = - derived_module.get_or_insert(exposed_by_module, derive_key); - - let specialized_lambda_set = *specialization_lambda_sets - .get(&lset_region) - .expect("lambda set region not resolved"); - - let specialized_ambient = derived_module.copy_lambda_set_ambient_function_to_subs( - specialized_lambda_set, - subs, - target_rank, - ); - - Ok(specialized_ambient) - } - - SpecializationTypeKey::Immediate(imm) => { - // Immediates are like opaques in that we can simply look up their type definition in - // the ability store, there is nothing new to synthesize. - // - // THEORY: if something can become an immediate, it will always be available in the - // local ability store, because the transformation is local (?) - let immediate_lambda_set_at_region = - phase.get_and_copy_ability_member_ambient_function(imm, lset_region, subs); - - Ok(immediate_lambda_set_at_region) - } - } -} - #[derive(Debug)] enum LocalDefVarsVec { Stack(arrayvec::ArrayVec), @@ -2452,7 +1832,6 @@ impl LocalDefVarsVec<(Symbol, Loc)> { } use std::cell::RefCell; -use std::collections::VecDeque; use std::ops::ControlFlow; std::thread_local! { /// Scratchpad arena so we don't need to allocate a new one all the time @@ -3849,7 +3228,7 @@ fn adjust_rank_content( /// Introduce some variables to Pools at the given rank. /// Also, set each of their ranks in Subs to be the given rank. -fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable]) { +pub(crate) fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable]) { let pool: &mut Vec = pools.get_mut(rank); for &var in vars.iter() { @@ -3859,7 +3238,7 @@ fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable]) pool.extend(vars); } -fn deep_copy_var_in( +pub(crate) fn deep_copy_var_in( subs: &mut Subs, rank: Rank, pools: &mut Pools, diff --git a/crates/compiler/solve/src/specialize.rs b/crates/compiler/solve/src/specialize.rs new file mode 100644 index 0000000000..7942f028e1 --- /dev/null +++ b/crates/compiler/solve/src/specialize.rs @@ -0,0 +1,641 @@ +//! Module [specialize] is resolves specialization lambda sets. + +use std::collections::VecDeque; + +use bumpalo::Bump; +use roc_can::{abilities::AbilitiesStore, module::ExposedByModule}; +use roc_debug_flags::{dbg_do, ROC_TRACE_COMPACTION}; +use roc_derive::SharedDerivedModule; +use roc_derive_key::{DeriveError, DeriveKey}; +use roc_error_macros::{internal_error, todo_abilities}; +use roc_module::symbol::{ModuleId, Symbol}; +use roc_types::{ + subs::{ + get_member_lambda_sets_at_region, Content, Descriptor, GetSubsSlice, LambdaSet, Mark, + OptVariable, Rank, Subs, SubsSlice, UlsOfVar, Variable, + }, + types::{AliasKind, MemberImpl, Uls}, +}; +use roc_unify::unify::{unify, Env as UEnv, Mode, MustImplementConstraints}; + +use crate::solve::{deep_copy_var_in, introduce, Pools}; + +/// What phase in the compiler is reaching out to specialize lambda sets? +/// This is important to distinguish subtle differences in the behavior of the solving algorithm. +// +// TODO the APIs of this trait suck, this needs a nice cleanup. +pub trait Phase { + /// The regular type-solving phase, or during some later phase of compilation. + /// During the solving phase we must anticipate that some information is still unknown and react to + /// that; during late phases, we expect that all information is resolved. + const IS_LATE: bool; + + fn with_module_abilities_store(&self, module: ModuleId, f: F) -> T + where + F: FnMut(&AbilitiesStore) -> T; + + /// Given a known lambda set's ambient function in an external module, copy that ambient + /// function into the given subs. + fn copy_lambda_set_ambient_function_to_home_subs( + &self, + external_lambda_set_var: Variable, + external_module_id: ModuleId, + home_subs: &mut Subs, + ) -> Variable; + + /// Find the ambient function var at a given region for an ability member definition (not a + /// specialization!), and copy that into the given subs. + fn get_and_copy_ability_member_ambient_function( + &self, + ability_member: Symbol, + region: u8, + home_subs: &mut Subs, + ) -> Variable; +} + +pub(crate) struct SolvePhase<'a> { + pub abilities_store: &'a AbilitiesStore, +} +impl Phase for SolvePhase<'_> { + const IS_LATE: bool = false; + + fn with_module_abilities_store(&self, _module: ModuleId, mut f: F) -> T + where + F: FnMut(&AbilitiesStore) -> T, + { + // During solving we're only aware of our module's abilities store. + f(self.abilities_store) + } + + fn copy_lambda_set_ambient_function_to_home_subs( + &self, + external_lambda_set_var: Variable, + _external_module_id: ModuleId, + home_subs: &mut Subs, + ) -> Variable { + // During solving we're only aware of our module's abilities store, the var must + // be in our module store. Even if the specialization lambda set comes from another + // module, we should have taken care to import it before starting solving in this module. + let LambdaSet { + ambient_function, .. + } = home_subs.get_lambda_set(external_lambda_set_var); + ambient_function + } + + fn get_and_copy_ability_member_ambient_function( + &self, + ability_member: Symbol, + region: u8, + home_subs: &mut Subs, + ) -> Variable { + // During solving we're only aware of our module's abilities store, the var must + // be in our module store. Even if the specialization lambda set comes from another + // module, we should have taken care to import it before starting solving in this module. + let member_def = self + .abilities_store + .member_def(ability_member) + .unwrap_or_else(|| { + internal_error!( + "{:?} is not resolved, or not an ability member!", + ability_member + ) + }); + let member_var = member_def.signature_var(); + + let region_lset = get_member_lambda_sets_at_region(home_subs, member_var, region); + + let LambdaSet { + ambient_function, .. + } = home_subs.get_lambda_set(region_lset); + + ambient_function + } +} + +#[cfg(debug_assertions)] +fn trace_compaction_step_1(subs: &Subs, c_a: Variable, uls_a: &[Variable]) { + let c_a = roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(c_a), subs); + let uls_a = uls_a + .iter() + .map(|v| { + format!( + "{:?}", + roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(*v), subs) + ) + }) + .collect::>() + .join(","); + eprintln!("===lambda set compaction==="); + eprintln!(" concrete type: {:?}", c_a); + eprintln!(" step 1:"); + eprintln!(" uls_a = {{ {} }}", uls_a); +} + +#[cfg(debug_assertions)] +fn trace_compaction_step_2(subs: &Subs, uls_a: &[Variable]) { + let uls_a = uls_a + .iter() + .map(|v| { + format!( + "{:?}", + roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(*v), subs) + ) + }) + .collect::>() + .join(","); + eprintln!(" step 2:"); + eprintln!(" uls_a' = {{ {} }}", uls_a); +} + +#[cfg(debug_assertions)] +fn trace_compaction_step_3start() { + eprintln!(" step 3:"); +} + +#[cfg(debug_assertions)] +fn trace_compaction_step_3iter_start( + subs: &Subs, + iteration_lambda_set: Variable, + t_f1: Variable, + t_f2: Variable, +) { + let iteration_lambda_set = roc_types::subs::SubsFmtContent( + subs.get_content_without_compacting(iteration_lambda_set), + subs, + ); + let t_f1 = roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(t_f1), subs); + let t_f2 = roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(t_f2), subs); + eprintln!(" - iteration: {:?}", iteration_lambda_set); + eprintln!(" {:?}", t_f1); + eprintln!(" ~ {:?}", t_f2); +} + +#[cfg(debug_assertions)] +#[rustfmt::skip] +fn trace_compaction_step_3iter_end(subs: &Subs, t_f_result: Variable, skipped: bool) { + let t_f_result = + roc_types::subs::SubsFmtContent(subs.get_content_without_compacting(t_f_result), subs); + if skipped { + eprintln!(" SKIP"); + } + eprintln!(" = {:?}\n", t_f_result); +} + +macro_rules! trace_compact { + (1. $subs:expr, $c_a:expr, $uls_a:expr) => {{ + dbg_do!(ROC_TRACE_COMPACTION, { + trace_compaction_step_1($subs, $c_a, $uls_a) + }) + }}; + (2. $subs:expr, $uls_a:expr) => {{ + dbg_do!(ROC_TRACE_COMPACTION, { + trace_compaction_step_2($subs, $uls_a) + }) + }}; + (3start.) => {{ + dbg_do!(ROC_TRACE_COMPACTION, { trace_compaction_step_3start() }) + }}; + (3iter_start. $subs:expr, $iteration_lset:expr, $t_f1:expr, $t_f2:expr) => {{ + dbg_do!(ROC_TRACE_COMPACTION, { + trace_compaction_step_3iter_start($subs, $iteration_lset, $t_f1, $t_f2) + }) + }}; + (3iter_end. $subs:expr, $t_f_result:expr) => {{ + dbg_do!(ROC_TRACE_COMPACTION, { + trace_compaction_step_3iter_end($subs, $t_f_result, false) + }) + }}; + (3iter_end_skipped. $subs:expr, $t_f_result:expr) => {{ + dbg_do!(ROC_TRACE_COMPACTION, { + trace_compaction_step_3iter_end($subs, $t_f_result, true) + }) + }}; +} + +#[inline(always)] +fn iter_concrete_of_unspecialized<'a>( + subs: &'a Subs, + c_a: Variable, + uls: &'a [Uls], +) -> impl Iterator { + uls.iter() + .filter(move |Uls(var, _, _)| subs.equivalent_without_compacting(*var, c_a)) +} + +/// Gets the unique unspecialized lambda resolving to concrete type `c_a` in a list of +/// unspecialized lambda sets. +#[inline(always)] +fn unique_unspecialized_lambda(subs: &Subs, c_a: Variable, uls: &[Uls]) -> Option { + let mut iter_concrete = iter_concrete_of_unspecialized(subs, c_a, uls); + let uls = iter_concrete.next()?; + debug_assert!(iter_concrete.next().is_none(), "multiple concrete"); + Some(*uls) +} + +#[must_use] +pub fn compact_lambda_sets_of_vars( + subs: &mut Subs, + derived_module: &SharedDerivedModule, + arena: &Bump, + pools: &mut Pools, + uls_of_var: UlsOfVar, + phase: &P, + exposed_by_module: &ExposedByModule, +) -> MustImplementConstraints { + // let mut seen = VecSet::default(); + let mut must_implement = MustImplementConstraints::default(); + + let mut uls_of_var_queue = VecDeque::with_capacity(uls_of_var.len()); + uls_of_var_queue.extend(uls_of_var.drain()); + + // Suppose a type variable `a` with `uls_of_var` mapping `uls_a = {l1, ... ln}` has been instantiated to a concrete type `C_a`. + while let Some((c_a, uls_a)) = uls_of_var_queue.pop_front() { + let c_a = subs.get_root_key_without_compacting(c_a); + // 1. Let each `l` in `uls_a` be of form `[solved_lambdas + ... + C:f:r + ...]`. + // NB: There may be multiple unspecialized lambdas of form `C:f:r, C:f1:r1, ..., C:fn:rn` in `l`. + // In this case, let `t1, ... tm` be the other unspecialized lambdas not of form `C:_:_`, + // that is, none of which are now specialized to the type `C`. Then, deconstruct + // `l` such that `l' = [solved_lambdas + t1 + ... + tm + C:f:r]` and `l1 = [[] + C:f1:r1], ..., ln = [[] + C:fn:rn]`. + // Replace `l` with `l', l1, ..., ln` in `uls_a`, flattened. + // TODO: the flattening step described above + let uls_a = uls_a.into_vec(); + trace_compact!(1. subs, c_a, &uls_a); + + // The flattening step - remove lambda sets that don't reference the concrete var, and for + // flatten lambda sets that reference it more than once. + let mut uls_a: Vec<_> = uls_a + .into_iter() + .flat_map(|lambda_set| { + let LambdaSet { + solved, + recursion_var, + unspecialized, + ambient_function, + } = subs.get_lambda_set(lambda_set); + let lambda_set_rank = subs.get_rank(lambda_set); + let unspecialized = subs.get_subs_slice(unspecialized); + // TODO: is it faster to traverse once, see if we only have one concrete lambda, and + // bail in that happy-path, rather than always splitting? + let (concrete, mut not_concrete): (Vec<_>, Vec<_>) = unspecialized + .iter() + .copied() + .partition(|Uls(var, _, _)| subs.equivalent_without_compacting(*var, c_a)); + if concrete.len() == 1 { + // No flattening needs to be done, just return the lambda set as-is + return vec![lambda_set]; + } + // Must flatten + concrete + .into_iter() + .enumerate() + .map(|(i, concrete_lambda)| { + let (var, unspecialized) = if i == 0 { + // The first lambda set contains one concrete lambda, plus all solved + // lambdas, plus all other unspecialized lambdas. + // l' = [solved_lambdas + t1 + ... + tm + C:f:r] + let unspecialized = SubsSlice::extend_new( + &mut subs.unspecialized_lambda_sets, + not_concrete + .drain(..) + .chain(std::iter::once(concrete_lambda)), + ); + (lambda_set, unspecialized) + } else { + // All the other lambda sets consists only of their respective concrete + // lambdas. + // ln = [[] + C:fn:rn] + let unspecialized = SubsSlice::extend_new( + &mut subs.unspecialized_lambda_sets, + [concrete_lambda], + ); + let var = subs.fresh(Descriptor { + content: Content::Error, + rank: lambda_set_rank, + mark: Mark::NONE, + copy: OptVariable::NONE, + }); + (var, unspecialized) + }; + + subs.set_content( + var, + Content::LambdaSet(LambdaSet { + solved, + recursion_var, + unspecialized, + ambient_function, + }), + ); + var + }) + .collect() + }) + .collect(); + + // 2. Now, each `l` in `uls_a` has a unique unspecialized lambda of form `C:f:r`. + // Sort `uls_a` primarily by `f` (arbitrary order), and secondarily by `r` in descending order. + uls_a.sort_by(|v1, v2| { + let unspec_1 = subs.get_subs_slice(subs.get_lambda_set(*v1).unspecialized); + let unspec_2 = subs.get_subs_slice(subs.get_lambda_set(*v2).unspecialized); + + let Uls(_, f1, r1) = unique_unspecialized_lambda(subs, c_a, unspec_1).unwrap(); + let Uls(_, f2, r2) = unique_unspecialized_lambda(subs, c_a, unspec_2).unwrap(); + + match f1.cmp(&f2) { + std::cmp::Ordering::Equal => { + // Order by descending order of region. + r2.cmp(&r1) + } + ord => ord, + } + }); + + trace_compact!(2. subs, &uls_a); + + // 3. For each `l` in `uls_a` with unique unspecialized lambda `C:f:r`: + // 1. Let `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. Remove `C:f:r` from `t_f1`'s lambda set. + // - For example, `(b' -[[] + Fo:f:2]-> {})` if `C:f:r=Fo:f:2`. Removing `Fo:f:2`, we get `(b' -[[]]-> {})`. + // 2. Let `t_f2` be the directly ambient function of the specialization lambda set resolved by `C:f:r`. + // - For example, `(b -[[] + b:g:1]-> {})` if `C:f:r=Fo:f:2`, running on example from above. + // 3. Unify `t_f1 ~ t_f2`. + trace_compact!(3start.); + for l in uls_a { + // let root_lset = subs.get_root_key_without_compacting(l); + // if seen.contains(&root_lset) { + // continue; + // } + + let (new_must_implement, new_uls_of_var) = compact_lambda_set( + subs, + derived_module, + arena, + pools, + c_a, + l, + phase, + exposed_by_module, + ); + + must_implement.extend(new_must_implement); + uls_of_var_queue.extend(new_uls_of_var.drain()); + + // seen.insert(root_lset); + } + } + + must_implement +} + +#[must_use] +#[allow(clippy::too_many_arguments)] +fn compact_lambda_set( + subs: &mut Subs, + derived_module: &SharedDerivedModule, + arena: &Bump, + pools: &mut Pools, + resolved_concrete: Variable, + this_lambda_set: Variable, + phase: &P, + exposed_by_module: &ExposedByModule, +) -> (MustImplementConstraints, UlsOfVar) { + // 3. For each `l` in `uls_a` with unique unspecialized lambda `C:f:r`: + // 1. Let `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. Remove `C:f:r` from `t_f1`'s lambda set. + // - For example, `(b' -[[] + Fo:f:2]-> {})` if `C:f:r=Fo:f:2`. Removing `Fo:f:2`, we get `(b' -[[]]-> {})`. + // 2. Let `t_f2` be the directly ambient function of the specialization lambda set resolved by `C:f:r`. + // - For example, `(b -[[] + b:g:1]-> {})` if `C:f:r=Fo:f:2`, from the algorithm's running example. + // 3. Unify `t_f1 ~ t_f2`. + let LambdaSet { + solved, + recursion_var, + unspecialized, + ambient_function: t_f1, + } = subs.get_lambda_set(this_lambda_set); + let target_rank = subs.get_rank(this_lambda_set); + + debug_assert!(!unspecialized.is_empty()); + + let unspecialized = subs.get_subs_slice(unspecialized); + + // 1. Let `t_f1` be the directly ambient function of the lambda set containing `C:f:r`. + let Uls(c, f, r) = unique_unspecialized_lambda(subs, resolved_concrete, unspecialized).unwrap(); + + debug_assert!(subs.equivalent_without_compacting(c, resolved_concrete)); + + // 1b. Remove `C:f:r` from `t_f1`'s lambda set. + let new_unspecialized: Vec<_> = unspecialized + .iter() + .filter(|Uls(v, _, _)| !subs.equivalent_without_compacting(*v, resolved_concrete)) + .copied() + .collect(); + debug_assert_eq!(new_unspecialized.len(), unspecialized.len() - 1); + let t_f1_lambda_set_without_concrete = LambdaSet { + solved, + recursion_var, + unspecialized: SubsSlice::extend_new( + &mut subs.unspecialized_lambda_sets, + new_unspecialized, + ), + ambient_function: t_f1, + }; + subs.set_content( + this_lambda_set, + Content::LambdaSet(t_f1_lambda_set_without_concrete), + ); + + let specialization_decision = make_specialization_decision(subs, c); + + let specialization_key = match specialization_decision { + SpecializeDecision::Specialize(key) => key, + SpecializeDecision::Drop => { + // Do nothing other than to remove the concrete lambda to drop from the lambda set, + // which we already did in 1b above. + trace_compact!(3iter_end_skipped. subs, t_f1); + return (Default::default(), Default::default()); + } + }; + + let specialization_ambient_function_var = get_specialization_lambda_set_ambient_function( + subs, + derived_module, + phase, + f, + r, + specialization_key, + exposed_by_module, + target_rank, + ); + + let t_f2 = match specialization_ambient_function_var { + Ok(lset) => lset, + Err(()) => { + // Do nothing other than to remove the concrete lambda to drop from the lambda set, + // which we already did in 1b above. + trace_compact!(3iter_end_skipped. subs, t_f1); + return (Default::default(), Default::default()); + } + }; + + // Ensure the specialized ambient function we'll unify with is not a generalized one, but one + // at the rank of the lambda set being compacted. + let t_f2 = deep_copy_var_in(subs, target_rank, pools, t_f2, arena); + + // 3. Unify `t_f1 ~ t_f2`. + trace_compact!(3iter_start. subs, this_lambda_set, t_f1, t_f2); + let (vars, new_must_implement_ability, new_lambda_sets_to_specialize, _meta) = + unify(&mut UEnv::new(subs), t_f1, t_f2, Mode::EQ) + .expect_success("ambient functions don't unify"); + trace_compact!(3iter_end. subs, t_f1); + + introduce(subs, target_rank, pools, &vars); + + (new_must_implement_ability, new_lambda_sets_to_specialize) +} + +enum SpecializationTypeKey { + Opaque(Symbol), + Derived(DeriveKey), + Immediate(Symbol), +} + +enum SpecializeDecision { + Specialize(SpecializationTypeKey), + Drop, +} + +fn make_specialization_decision(subs: &Subs, var: Variable) -> SpecializeDecision { + use Content::*; + use SpecializationTypeKey::*; + match subs.get_content_without_compacting(var) { + Alias(opaque, _, _, AliasKind::Opaque) if opaque.module_id() != ModuleId::NUM => { + SpecializeDecision::Specialize(Opaque(*opaque)) + } + Structure(_) | Alias(_, _, _, _) => { + // This is a structural type, find the name of the derived ability function it + // should use. + match roc_derive_key::Derived::encoding(subs, var) { + Ok(derived) => match derived { + roc_derive_key::Derived::Immediate(imm) => { + SpecializeDecision::Specialize(Immediate(imm)) + // todo!("deal with lambda set extraction from immediates") + } + roc_derive_key::Derived::Key(derive_key) => { + SpecializeDecision::Specialize(Derived(derive_key)) + } + }, + Err(DeriveError::UnboundVar) => { + // not specialized yet, but that also means that it can't possibly be derivable + // at this point? + // TODO: is this right? Revisit if it causes us problems in the future. + SpecializeDecision::Drop + } + Err(DeriveError::Underivable) => { + // we should have reported an error for this; drop the lambda set. + SpecializeDecision::Drop + } + } + } + Error => SpecializeDecision::Drop, + FlexAbleVar(_, _) + | RigidAbleVar(..) + | FlexVar(..) + | RigidVar(..) + | RecursionVar { .. } + | LambdaSet(..) + | RangedNumber(..) => { + internal_error!("unexpected") + } + } +} + +#[allow(clippy::too_many_arguments)] +fn get_specialization_lambda_set_ambient_function( + subs: &mut Subs, + derived_module: &SharedDerivedModule, + phase: &P, + ability_member: Symbol, + lset_region: u8, + specialization_key: SpecializationTypeKey, + exposed_by_module: &ExposedByModule, + target_rank: Rank, +) -> Result { + match specialization_key { + SpecializationTypeKey::Opaque(opaque) => { + let opaque_home = opaque.module_id(); + let external_specialized_lset = + phase.with_module_abilities_store(opaque_home, |abilities_store| { + let impl_key = roc_can::abilities::ImplKey { + opaque, + ability_member, + }; + let opt_specialization = + abilities_store.get_implementation(impl_key); + match (P::IS_LATE, opt_specialization) { + (false, None) => { + // doesn't specialize, we'll have reported an error for this + Err(()) + } + (true, None) => { + internal_error!( + "expected to know a specialization for {:?}#{:?}, but it wasn't found", + opaque, + ability_member, + ); + } + (_, Some(member_impl)) => match member_impl { + MemberImpl::Impl(spec_symbol) => { + let specialization = + abilities_store.specialization_info(*spec_symbol).expect("expected custom implementations to always have complete specialization info by this point"); + + let specialized_lambda_set = *specialization + .specialization_lambda_sets + .get(&lset_region) + .expect("lambda set region not resolved"); + Ok(specialized_lambda_set) + } + MemberImpl::Derived => todo_abilities!(), + MemberImpl::Error => todo_abilities!(), + }, + } + })?; + + let specialized_ambient = phase.copy_lambda_set_ambient_function_to_home_subs( + external_specialized_lset, + opaque_home, + subs, + ); + + Ok(specialized_ambient) + } + + SpecializationTypeKey::Derived(derive_key) => { + let mut derived_module = derived_module.lock().unwrap(); + + let (_, _, specialization_lambda_sets) = + derived_module.get_or_insert(exposed_by_module, derive_key); + + let specialized_lambda_set = *specialization_lambda_sets + .get(&lset_region) + .expect("lambda set region not resolved"); + + let specialized_ambient = derived_module.copy_lambda_set_ambient_function_to_subs( + specialized_lambda_set, + subs, + target_rank, + ); + + Ok(specialized_ambient) + } + + SpecializationTypeKey::Immediate(imm) => { + // Immediates are like opaques in that we can simply look up their type definition in + // the ability store, there is nothing new to synthesize. + // + // THEORY: if something can become an immediate, it will always be available in the + // local ability store, because the transformation is local (?) + let immediate_lambda_set_at_region = + phase.get_and_copy_ability_member_ambient_function(imm, lset_region, subs); + + Ok(immediate_lambda_set_at_region) + } + } +}