roc/crates/compiler/derive/src/util.rs
2023-03-22 16:43:52 -05:00

188 lines
6.9 KiB
Rust

use roc_can::{abilities::SpecializationLambdaSets, module::ExposedByModule};
use roc_error_macros::internal_error;
use roc_module::symbol::{IdentIds, Symbol};
use roc_types::{
subs::{instantiate_rigids, Subs, Variable},
types::Polarity,
};
use crate::DERIVED_SYNTH;
/// An environment representing the Derived_synth module, for use in building derived
/// implementations.
pub(crate) struct Env<'a> {
/// NB: This **must** be subs for the derive module!
pub subs: &'a mut Subs,
pub exposed_types: &'a ExposedByModule,
pub derived_ident_ids: &'a mut IdentIds,
}
impl Env<'_> {
pub fn new_symbol(&mut self, name_hint: impl std::string::ToString) -> Symbol {
if cfg!(any(
debug_assertions,
test,
feature = "debug-derived-symbols"
)) {
let mut i = 0;
let hint = name_hint.to_string();
let debug_name = loop {
i += 1;
let name = if i == 1 {
hint.clone()
} else {
format!("{}{}", hint, i)
};
if self.derived_ident_ids.get_id(&name).is_none() {
break name;
}
};
let ident_id = self.derived_ident_ids.get_or_insert(&debug_name);
Symbol::new(DERIVED_SYNTH, ident_id)
} else {
self.unique_symbol()
}
}
pub fn unique_symbol(&mut self) -> Symbol {
let ident_id = self.derived_ident_ids.gen_unique();
Symbol::new(DERIVED_SYNTH, ident_id)
}
pub fn import_builtin_symbol_var(&mut self, symbol: Symbol) -> Variable {
let module_id = symbol.module_id();
debug_assert!(module_id.is_builtin());
let module_types = &self
.exposed_types
.get(&module_id)
.unwrap()
.exposed_types_storage_subs;
let storage_var = module_types.stored_vars_by_symbol.get(&symbol).unwrap();
let imported = module_types
.storage_subs
.export_variable_to_directly_to_use_site(self.subs, *storage_var);
instantiate_rigids(self.subs, imported.variable);
imported.variable
}
pub fn unify(&mut self, left: Variable, right: Variable) {
use roc_unify::unify::{unify, Env, Mode, Unified};
let unified = unify(
&mut Env::new(self.subs),
left,
right,
Mode::EQ,
Polarity::OF_PATTERN,
);
match unified {
Unified::Success {
vars: _,
must_implement_ability: _,
lambda_sets_to_specialize,
extra_metadata: _,
} => {
if !lambda_sets_to_specialize.is_empty() {
internal_error!("Did not expect derivers to need to specialize unspecialized lambda sets, but we got some: {:?}", lambda_sets_to_specialize)
}
}
Unified::Failure(..) => {
internal_error!("Unification failed in deriver - that's a deriver bug!")
}
}
}
pub fn get_specialization_lambda_sets(
&mut self,
specialization_type: Variable,
ability_member: Symbol,
) -> SpecializationLambdaSets {
use roc_unify::unify::{unify_introduced_ability_specialization, Env, Mode, Unified};
let member_signature = self.import_builtin_symbol_var(ability_member);
let unified = unify_introduced_ability_specialization(
&mut Env::new(self.subs),
member_signature,
specialization_type,
Mode::EQ,
);
match unified {
Unified::Success {
vars: _,
must_implement_ability: _,
lambda_sets_to_specialize: _lambda_sets_to_specialize,
extra_metadata: specialization_lsets,
} => {
let specialization_lsets: SpecializationLambdaSets = specialization_lsets
.0
.into_iter()
.map(|((spec_member, region), var)| {
debug_assert_eq!(spec_member, ability_member);
(region, var)
})
.collect();
// Since we're doing `{foo} ~ a | a has Encoding`, we may see "lambda sets to
// specialize" for e.g. `{foo}:toEncoder:1`, but these are actually just the
// specialization lambda sets, so we don't need to do any extra work!
//
// If there are other lambda sets to specialize in here, that's unexpected, because
// that means we would have been deriving something like `toEncoder {foo: bar}`,
// and now seen that we needed `toEncoder bar` where `bar` is a concrete type. But
// we only expect `bar` to polymorphic at this stage!
//
// TODO: it would be better if `unify` could prune these for us. See also
// https://github.com/roc-lang/roc/issues/3207; that is a blocker for this TODO.
#[cfg(debug_assertions)]
{
for (spec_var, lambda_sets) in _lambda_sets_to_specialize.drain() {
for lambda_set in lambda_sets {
let belongs_to_specialized_lambda_sets =
specialization_lsets.iter().any(|(_, var)| {
self.subs.get_root_key_without_compacting(*var)
== self.subs.get_root_key_without_compacting(lambda_set)
});
debug_assert!(belongs_to_specialized_lambda_sets,
"Did not expect derivers to need to specialize unspecialized lambda sets, but we got one: {:?} for {:?}", lambda_set, spec_var)
}
}
}
specialization_lsets
}
Unified::Failure(..) => {
internal_error!("Unification failed in deriver - that's a deriver bug!")
}
}
}
/// Creates an extension variable for a tag union or record.
///
/// Derivers should always construct tag union and record types such that they are closed.
/// If the `open-extension-vars` feature is turned on, flex extension vars will be
/// returned; otherwise, the appropriate closed extension variable for the type will be
/// returned.
#[inline(always)]
pub fn new_ext_var(&mut self, kind: ExtensionKind) -> Variable {
if cfg!(feature = "open-extension-vars") {
self.subs.fresh_unnamed_flex_var()
} else {
match kind {
ExtensionKind::Record => Variable::EMPTY_RECORD,
ExtensionKind::TagUnion => Variable::EMPTY_TAG_UNION,
}
}
}
}
pub(crate) enum ExtensionKind {
Record,
TagUnion,
}