roc/crates/compiler/unify/src/unify.rs
2022-11-01 12:06:59 -05:00

3510 lines
124 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use bitflags::bitflags;
use roc_collections::VecMap;
use roc_debug_flags::dbg_do;
#[cfg(debug_assertions)]
use roc_debug_flags::{ROC_PRINT_MISMATCHES, ROC_PRINT_UNIFICATIONS};
use roc_error_macros::internal_error;
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{ModuleId, Symbol};
use roc_types::num::{FloatWidth, IntLitWidth, NumericRange};
use roc_types::subs::Content::{self, *};
use roc_types::subs::{
AliasVariables, Descriptor, ErrorTypeContext, FlatType, GetSubsSlice, LambdaSet, Mark,
OptVariable, RecordFields, Subs, SubsIndex, SubsSlice, UlsOfVar, UnionLabels, UnionLambdas,
UnionTags, Variable, VariableSubsSlice,
};
use roc_types::types::{
AliasKind, DoesNotImplementAbility, ErrorType, Mismatch, Polarity, RecordField, Uls,
};
macro_rules! mismatch {
() => {{
dbg_do!(ROC_PRINT_MISMATCHES, {
eprintln!(
"Mismatch in {} Line {} Column {}",
file!(),
line!(),
column!()
);
});
Outcome {
mismatches: vec![Mismatch::TypeMismatch],
..Outcome::default()
}
}};
($msg:expr) => {{
dbg_do!(ROC_PRINT_MISMATCHES, {
eprintln!(
"Mismatch in {} Line {} Column {}",
file!(),
line!(),
column!()
);
eprintln!($msg);
eprintln!("");
});
Outcome {
mismatches: vec![Mismatch::TypeMismatch],
..Outcome::default()
}
}};
($msg:expr,) => {{
mismatch!($msg)
}};
($msg:expr, $($arg:tt)*) => {{
dbg_do!(ROC_PRINT_MISMATCHES, {
eprintln!(
"Mismatch in {} Line {} Column {}",
file!(),
line!(),
column!()
);
eprintln!($msg, $($arg)*);
eprintln!("");
});
Outcome {
mismatches: vec![Mismatch::TypeMismatch],
..Outcome::default()
}
}};
(%not_able, $var:expr, $abilities:expr, $msg:expr, $($arg:tt)*) => {{
dbg_do!(ROC_PRINT_MISMATCHES, {
eprintln!(
"Mismatch in {} Line {} Column {}",
file!(),
line!(),
column!()
);
eprintln!($msg, $($arg)*);
eprintln!("");
});
let mut mismatches = Vec::with_capacity(1 + $abilities.len());
mismatches.push(Mismatch::TypeMismatch);
for ability in $abilities {
mismatches.push(Mismatch::DoesNotImplementAbiity($var, *ability));
}
Outcome {
mismatches,
..Outcome::default()
}
}}
}
type Pool = Vec<Variable>;
bitflags! {
pub struct Mode : u8 {
/// Instructs the unifier to solve two types for equality.
///
/// For example, { n : Str }a ~ { n: Str, m : Str } will solve "a" to "{ m : Str }".
const EQ = 1 << 0;
/// Instructs the unifier to treat the right-hand-side of a constraint as
/// present in the left-hand-side, rather than strictly equal.
///
/// For example, t1 += [A Str] says we should "add" the tag "A Str" to the type of "t1".
const PRESENT = 1 << 1;
/// Like [`Mode::EQ`], but also instructs the unifier that the ambient lambda set
/// specialization algorithm is running. This has implications for the unification of
/// unspecialized lambda sets; see [`unify_unspecialized_lambdas`].
const LAMBDA_SET_SPECIALIZATION = Mode::EQ.bits | (1 << 2);
}
}
impl Mode {
fn is_eq(&self) -> bool {
debug_assert!(!self.contains(Mode::EQ | Mode::PRESENT));
self.contains(Mode::EQ)
}
fn is_present(&self) -> bool {
debug_assert!(!self.contains(Mode::EQ | Mode::PRESENT));
self.contains(Mode::PRESENT)
}
fn is_lambda_set_specialization(&self) -> bool {
debug_assert!(!self.contains(Mode::EQ | Mode::PRESENT));
self.contains(Mode::LAMBDA_SET_SPECIALIZATION)
}
fn as_eq(self) -> Self {
(self - Mode::PRESENT) | Mode::EQ
}
#[cfg(debug_assertions)]
fn pretty_print(&self) -> &str {
if self.contains(Mode::EQ) {
"~"
} else if self.contains(Mode::PRESENT) {
"+="
} else {
unreachable!("Bad mode!")
}
}
}
#[derive(Debug)]
pub struct Context {
first: Variable,
first_desc: Descriptor,
second: Variable,
second_desc: Descriptor,
mode: Mode,
}
pub trait MetaCollector: Default + std::fmt::Debug {
/// Whether we are performing `member ~ specialization` where `member` is an ability member
/// signature and `specialization` is an ability specialization for a given type. When this is
/// the case, given a lambda set unification like
/// `[[] + a:member:1] ~ [specialization-lambda-set]`, only the specialization lambda set will
/// be kept around, and the record `(member, 1) => specialization-lambda-set` will be
/// associated via [`Self::record_specialization_lambda_set`].
const UNIFYING_SPECIALIZATION: bool;
const IS_LATE: bool;
fn record_specialization_lambda_set(&mut self, member: Symbol, region: u8, var: Variable);
fn record_changed_variable(&mut self, subs: &Subs, var: Variable);
fn union(&mut self, other: Self);
}
#[derive(Default, Debug)]
pub struct NoCollector;
impl MetaCollector for NoCollector {
const UNIFYING_SPECIALIZATION: bool = false;
const IS_LATE: bool = false;
#[inline(always)]
fn record_specialization_lambda_set(&mut self, _member: Symbol, _region: u8, _var: Variable) {}
#[inline(always)]
fn record_changed_variable(&mut self, _subs: &Subs, _var: Variable) {}
#[inline(always)]
fn union(&mut self, _other: Self) {}
}
#[derive(Default, Debug)]
pub struct SpecializationLsetCollector(pub VecMap<(Symbol, u8), Variable>);
impl MetaCollector for SpecializationLsetCollector {
const UNIFYING_SPECIALIZATION: bool = true;
const IS_LATE: bool = false;
#[inline(always)]
fn record_specialization_lambda_set(&mut self, member: Symbol, region: u8, var: Variable) {
self.0.insert((member, region), var);
}
#[inline(always)]
fn record_changed_variable(&mut self, _subs: &Subs, _var: Variable) {}
#[inline(always)]
fn union(&mut self, other: Self) {
for (k, v) in other.0.into_iter() {
let _old = self.0.insert(k, v);
debug_assert!(_old.is_none(), "overwriting known lambda set");
}
}
}
#[derive(Debug)]
pub enum Unified<M: MetaCollector = NoCollector> {
Success {
vars: Pool,
must_implement_ability: MustImplementConstraints,
lambda_sets_to_specialize: UlsOfVar,
/// The vast majority of the time the extra metadata is empty, so we make unification
/// polymorphic over metadata collection to avoid unnecessary memory usage.
extra_metadata: M,
},
Failure(Pool, ErrorType, ErrorType, DoesNotImplementAbility),
BadType(Pool, roc_types::types::Problem),
}
impl<M: MetaCollector> Unified<M> {
pub fn expect_success(
self,
err_msg: &'static str,
) -> (Pool, MustImplementConstraints, UlsOfVar, M) {
match self {
Unified::Success {
vars,
must_implement_ability,
lambda_sets_to_specialize,
extra_metadata,
} => (
vars,
must_implement_ability,
lambda_sets_to_specialize,
extra_metadata,
),
_ => internal_error!("{}", err_msg),
}
}
}
/// Type obligated to implement an ability.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Obligated {
/// Opaque types can either define custom implementations for an ability, or ask the compiler
/// to generate an implementation of a builtin ability for them. In any case they have unique
/// obligation rules for abilities.
Opaque(Symbol),
/// A structural type for which the compiler can at most generate an adhoc implementation of
/// a builtin ability.
Adhoc(Variable),
}
/// Specifies that `type` must implement the ability `ability`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct MustImplementAbility {
pub typ: Obligated,
pub ability: Symbol,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct MustImplementConstraints(Vec<MustImplementAbility>);
impl MustImplementConstraints {
pub fn push(&mut self, must_implement: MustImplementAbility) {
self.0.push(must_implement)
}
pub fn extend(&mut self, other: Self) {
self.0.extend(other.0)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn get_unique(mut self) -> Vec<MustImplementAbility> {
self.0.sort();
self.0.dedup();
self.0
}
pub fn iter_for_ability(&self, ability: Symbol) -> impl Iterator<Item = &MustImplementAbility> {
self.0.iter().filter(move |mia| mia.ability == ability)
}
}
#[derive(Debug, Default)]
pub struct Outcome<M: MetaCollector> {
mismatches: Vec<Mismatch>,
/// We defer these checks until the end of a solving phase.
/// NOTE: this vector is almost always empty!
must_implement_ability: MustImplementConstraints,
/// We defer resolution of these lambda sets to the caller of [unify].
/// See also [merge_flex_able_with_concrete].
lambda_sets_to_specialize: UlsOfVar,
extra_metadata: M,
}
impl<M: MetaCollector> Outcome<M> {
fn union(&mut self, other: Self) {
self.mismatches.extend(other.mismatches);
self.must_implement_ability
.extend(other.must_implement_ability);
self.lambda_sets_to_specialize
.union(other.lambda_sets_to_specialize);
self.extra_metadata.union(other.extra_metadata);
}
}
pub struct Env<'a> {
pub subs: &'a mut Subs,
compute_outcome_only: bool,
}
impl<'a> Env<'a> {
pub fn new(subs: &'a mut Subs) -> Self {
Self {
subs,
compute_outcome_only: false,
}
}
// Computes a closure in outcome-only mode. Unifications run in outcome-only mode will check
// for unifiability, but will not modify type variables or merge them.
pub fn with_outcome_only<T>(&mut self, f: impl FnOnce(&mut Self) -> T) -> T {
self.compute_outcome_only = true;
let result = f(self);
self.compute_outcome_only = false;
result
}
}
/// Unifies two types.
/// The [mode][Mode] enables or disables certain extensional features of unification.
///
/// `observed_pol` describes the [polarity][Polarity] of the type observed to be under unification.
/// This is only relevant for producing error types, and is not material to the unification
/// algorithm.
#[inline(always)]
pub fn unify(
env: &mut Env,
var1: Variable,
var2: Variable,
mode: Mode,
observed_pol: Polarity,
) -> Unified {
unify_help(env, var1, var2, mode, observed_pol)
}
#[inline(always)]
#[must_use]
pub fn unify_introduced_ability_specialization(
env: &mut Env,
ability_member_signature: Variable,
specialization_var: Variable,
mode: Mode,
) -> Unified<SpecializationLsetCollector> {
unify_help(
env,
ability_member_signature,
specialization_var,
mode,
Polarity::OF_VALUE,
)
}
#[inline(always)]
#[must_use]
pub fn unify_with_collector<M: MetaCollector>(
env: &mut Env,
var1: Variable,
var2: Variable,
mode: Mode,
observed_pol: Polarity,
) -> Unified<M> {
unify_help(env, var1, var2, mode, observed_pol)
}
#[inline(always)]
#[must_use]
fn unify_help<M: MetaCollector>(
env: &mut Env,
var1: Variable,
var2: Variable,
mode: Mode,
observed_pol: Polarity,
) -> Unified<M> {
let mut vars = Vec::new();
let Outcome {
mismatches,
must_implement_ability,
lambda_sets_to_specialize,
extra_metadata,
} = unify_pool(env, &mut vars, var1, var2, mode);
if mismatches.is_empty() {
Unified::Success {
vars,
must_implement_ability,
lambda_sets_to_specialize,
extra_metadata,
}
} else {
let error_context = if mismatches.contains(&Mismatch::TypeNotInRange) {
ErrorTypeContext::ExpandRanges
} else {
ErrorTypeContext::None
};
let (type1, mut problems) =
env.subs
.var_to_error_type_contextual(var1, error_context, observed_pol);
let (type2, problems2) =
env.subs
.var_to_error_type_contextual(var2, error_context, observed_pol);
problems.extend(problems2);
env.subs.union(var1, var2, Content::Error.into());
if !problems.is_empty() {
Unified::BadType(vars, problems.remove(0))
} else {
let do_not_implement_ability = mismatches
.into_iter()
.filter_map(|mismatch| match mismatch {
Mismatch::DoesNotImplementAbiity(var, ab) => {
let (err_type, _new_problems) =
env.subs
.var_to_error_type_contextual(var, error_context, observed_pol);
Some((err_type, ab))
}
_ => None,
})
.collect();
Unified::Failure(vars, type1, type2, do_not_implement_ability)
}
}
}
#[inline(always)]
#[must_use]
pub fn unify_pool<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
var1: Variable,
var2: Variable,
mode: Mode,
) -> Outcome<M> {
if env.subs.equivalent(var1, var2) {
Outcome::default()
} else {
let ctx = Context {
first: var1,
first_desc: env.subs.get(var1),
second: var2,
second_desc: env.subs.get(var2),
mode,
};
unify_context(env, pool, ctx)
}
}
/// Set `ROC_PRINT_UNIFICATIONS` in debug runs to print unifications as they start and complete as
/// a tree to stderr.
/// NOTE: Only run this on individual tests! Run on multiple threads, this would clobber each others' output.
#[cfg(debug_assertions)]
fn debug_print_unified_types<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
opt_outcome: Option<&Outcome<M>>,
) {
use roc_types::subs::SubsFmtContent;
static mut UNIFICATION_DEPTH: usize = 0;
dbg_do!(ROC_PRINT_UNIFICATIONS, {
let prefix = match opt_outcome {
None => "",
Some(outcome) if outcome.mismatches.is_empty() => "",
Some(_) => "",
};
let depth = unsafe { UNIFICATION_DEPTH };
let indent = 2;
let (use_depth, new_depth) = if opt_outcome.is_none() {
(depth, depth + indent)
} else {
(depth - indent, depth - indent)
};
// NOTE: names are generated here (when creating an error type) and that modifies names
// generated by pretty_print.rs. So many test will fail with changes in variable names when
// this block runs.
// let (type1, _problems1) = subs.var_to_error_type(ctx.first);
// let (type2, _problems2) = subs.var_to_error_type(ctx.second);
// println!("\n --------------- \n");
// dbg!(ctx.first, type1);
// println!("\n --- \n");
// dbg!(ctx.second, type2);
// println!("\n --------------- \n");
let content_1 = env.subs.get(ctx.first).content;
let content_2 = env.subs.get(ctx.second).content;
let mode = ctx.mode.pretty_print();
eprintln!(
"{}{}({:?}-{:?}): {:?} {:?} {} {:?} {:?}",
" ".repeat(use_depth),
prefix,
env.subs.get_root_key_without_compacting(ctx.first),
env.subs.get_root_key_without_compacting(ctx.second),
ctx.first,
SubsFmtContent(&content_1, env.subs),
mode,
ctx.second,
SubsFmtContent(&content_2, env.subs),
);
unsafe { UNIFICATION_DEPTH = new_depth };
})
}
#[must_use]
fn unify_context<M: MetaCollector>(env: &mut Env, pool: &mut Pool, ctx: Context) -> Outcome<M> {
#[cfg(debug_assertions)]
debug_print_unified_types::<M>(env, &ctx, None);
// This #[allow] is needed in release builds, where `result` is no longer used.
#[allow(clippy::let_and_return)]
let result = match &ctx.first_desc.content {
FlexVar(opt_name) => unify_flex(env, &ctx, opt_name, &ctx.second_desc.content),
FlexAbleVar(opt_name, abilities) => {
unify_flex_able(env, &ctx, opt_name, *abilities, &ctx.second_desc.content)
}
RecursionVar {
opt_name,
structure,
} => unify_recursion(
env,
pool,
&ctx,
opt_name,
*structure,
&ctx.second_desc.content,
),
RigidVar(name) => unify_rigid(env, &ctx, name, &ctx.second_desc.content),
RigidAbleVar(name, abilities) => {
unify_rigid_able(env, &ctx, name, *abilities, &ctx.second_desc.content)
}
Structure(flat_type) => {
unify_structure(env, pool, &ctx, flat_type, &ctx.second_desc.content)
}
Alias(symbol, args, real_var, AliasKind::Structural) => {
unify_alias(env, pool, &ctx, *symbol, *args, *real_var)
}
Alias(symbol, args, real_var, AliasKind::Opaque) => {
unify_opaque(env, pool, &ctx, *symbol, *args, *real_var)
}
LambdaSet(lset) => unify_lambda_set(env, pool, &ctx, *lset, &ctx.second_desc.content),
&RangedNumber(range_vars) => unify_ranged_number(env, pool, &ctx, range_vars),
Error => {
// Error propagates. Whatever we're comparing it to doesn't matter!
merge(env, &ctx, Error)
}
};
#[cfg(debug_assertions)]
debug_print_unified_types(env, &ctx, Some(&result));
result
}
fn not_in_range_mismatch<M: MetaCollector>() -> Outcome<M> {
Outcome {
mismatches: vec![Mismatch::TypeNotInRange],
must_implement_ability: Default::default(),
lambda_sets_to_specialize: Default::default(),
extra_metadata: Default::default(),
}
}
#[inline(always)]
#[must_use]
fn unify_ranged_number<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
range_vars: NumericRange,
) -> Outcome<M> {
let other_content = &ctx.second_desc.content;
match other_content {
FlexVar(_) => {
// Ranged number wins
merge(env, ctx, RangedNumber(range_vars))
}
RigidVar(name) => {
// Int a vs Int <range>, the rigid wins
merge(env, ctx, RigidVar(*name))
}
RecursionVar { .. } | Alias(..) | Structure(..) | RigidAbleVar(..) | FlexAbleVar(..) => {
check_and_merge_valid_range(env, pool, ctx, ctx.first, range_vars, ctx.second)
}
&RangedNumber(other_range_vars) => match range_vars.intersection(&other_range_vars) {
Some(range) => merge(env, ctx, RangedNumber(range)),
None => not_in_range_mismatch(),
},
LambdaSet(..) => mismatch!(),
Error => merge(env, ctx, Error),
}
}
fn check_and_merge_valid_range<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
range_var: Variable,
range: NumericRange,
var: Variable,
) -> Outcome<M> {
use Content::*;
let content = *env.subs.get_content_without_compacting(var);
macro_rules! merge_if {
($cond:expr) => {
if $cond {
merge(env, ctx, content)
} else {
not_in_range_mismatch()
}
};
}
match content {
RangedNumber(other_range) => match range.intersection(&other_range) {
Some(r) => {
if r == range {
merge(env, ctx, RangedNumber(range))
} else {
merge(env, ctx, RangedNumber(other_range))
}
}
None => not_in_range_mismatch(),
},
Alias(symbol, args, _real_var, kind) => match symbol {
Symbol::NUM_I8 | Symbol::NUM_SIGNED8 => {
merge_if!(range.contains_int_width(IntLitWidth::I8))
}
Symbol::NUM_U8 | Symbol::NUM_UNSIGNED8 => {
merge_if!(range.contains_int_width(IntLitWidth::U8))
}
Symbol::NUM_I16 | Symbol::NUM_SIGNED16 => {
merge_if!(range.contains_int_width(IntLitWidth::I16))
}
Symbol::NUM_U16 | Symbol::NUM_UNSIGNED16 => {
merge_if!(range.contains_int_width(IntLitWidth::U16))
}
Symbol::NUM_I32 | Symbol::NUM_SIGNED32 => {
merge_if!(range.contains_int_width(IntLitWidth::I32))
}
Symbol::NUM_U32 | Symbol::NUM_UNSIGNED32 => {
merge_if!(range.contains_int_width(IntLitWidth::U32))
}
Symbol::NUM_I64 | Symbol::NUM_SIGNED64 => {
merge_if!(range.contains_int_width(IntLitWidth::I64))
}
Symbol::NUM_NAT | Symbol::NUM_NATURAL => {
merge_if!(range.contains_int_width(IntLitWidth::Nat))
}
Symbol::NUM_U64 | Symbol::NUM_UNSIGNED64 => {
merge_if!(range.contains_int_width(IntLitWidth::U64))
}
Symbol::NUM_I128 | Symbol::NUM_SIGNED128 => {
merge_if!(range.contains_int_width(IntLitWidth::I128))
}
Symbol::NUM_U128 | Symbol::NUM_UNSIGNED128 => {
merge_if!(range.contains_int_width(IntLitWidth::U128))
}
Symbol::NUM_DEC | Symbol::NUM_DECIMAL => {
merge_if!(range.contains_float_width(FloatWidth::Dec))
}
Symbol::NUM_F32 | Symbol::NUM_BINARY32 => {
merge_if!(range.contains_float_width(FloatWidth::F32))
}
Symbol::NUM_F64 | Symbol::NUM_BINARY64 => {
merge_if!(range.contains_float_width(FloatWidth::F64))
}
Symbol::NUM_FRAC | Symbol::NUM_FLOATINGPOINT => match range {
NumericRange::IntAtLeastSigned(_) | NumericRange::IntAtLeastEitherSign(_) => {
mismatch!()
}
NumericRange::NumAtLeastSigned(_) | NumericRange::NumAtLeastEitherSign(_) => {
debug_assert_eq!(args.len(), 1);
let arg = env.subs.get_subs_slice(args.all_variables())[0];
let new_range_var = wrap_range_var(env, symbol, range_var, kind);
unify_pool(env, pool, new_range_var, arg, ctx.mode)
}
},
Symbol::NUM_NUM => {
debug_assert_eq!(args.len(), 1);
let arg = env.subs.get_subs_slice(args.all_variables())[0];
let new_range_var = wrap_range_var(env, symbol, range_var, kind);
unify_pool(env, pool, new_range_var, arg, ctx.mode)
}
Symbol::NUM_INT | Symbol::NUM_INTEGER => {
debug_assert_eq!(args.len(), 1);
let arg = env.subs.get_subs_slice(args.all_variables())[0];
let new_range_var = wrap_range_var(env, symbol, range_var, kind);
unify_pool(env, pool, new_range_var, arg, ctx.mode)
}
_ => mismatch!(),
},
_ => mismatch!(),
}
}
/// Push a number range var down into a number type, so as to preserve type hierarchy structure.
/// For example when we have Num (Int a) ~ Num (NumericRange <U128>), we want to produce
/// Num (Int (NumericRange <U128>))
/// on the right (which this function does) and then unify
/// Num (Int a) ~ Num (Int (NumericRange <U128>))
fn wrap_range_var(
env: &mut Env,
symbol: Symbol,
range_var: Variable,
alias_kind: AliasKind,
) -> Variable {
let range_desc = env.subs.get(range_var);
let new_range_var = env.subs.fresh(range_desc);
let var_slice = AliasVariables::insert_into_subs(env.subs, [new_range_var], [], []);
env.subs.set_content(
range_var,
Alias(symbol, var_slice, new_range_var, alias_kind),
);
new_range_var
}
#[inline(always)]
#[allow(clippy::too_many_arguments)]
#[must_use]
fn unify_two_aliases<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
kind: AliasKind,
symbol: Symbol,
args: AliasVariables,
real_var: Variable,
other_args: AliasVariables,
other_real_var: Variable,
) -> Outcome<M> {
if args.len() == other_args.len() {
let mut outcome = Outcome::default();
let args_it = args
.type_variables()
.into_iter()
.zip(other_args.type_variables().into_iter());
let lambda_set_it = args
.lambda_set_variables()
.into_iter()
.zip(other_args.lambda_set_variables().into_iter());
let infer_ext_in_output_vars_it = (args.infer_ext_in_output_variables().into_iter())
.zip(other_args.infer_ext_in_output_variables().into_iter());
let mut merged_args = Vec::with_capacity(args.type_variables().len());
let mut merged_lambda_set_args = Vec::with_capacity(args.lambda_set_variables().len());
let mut merged_infer_ext_in_output_vars =
Vec::with_capacity(args.infer_ext_in_output_variables().len());
debug_assert_eq!(
merged_args.capacity()
+ merged_lambda_set_args.capacity()
+ merged_infer_ext_in_output_vars.capacity(),
args.all_variables_len as _,
);
for (l, r) in args_it {
let l_var = env.subs[l];
let r_var = env.subs[r];
outcome.union(unify_pool(env, pool, l_var, r_var, ctx.mode));
let merged_var = choose_merged_var(env.subs, l_var, r_var);
merged_args.push(merged_var);
}
for (l, r) in lambda_set_it {
let l_var = env.subs[l];
let r_var = env.subs[r];
outcome.union(unify_pool(env, pool, l_var, r_var, ctx.mode));
let merged_var = choose_merged_var(env.subs, l_var, r_var);
merged_lambda_set_args.push(merged_var);
}
for (l, r) in infer_ext_in_output_vars_it {
let l_var = env.subs[l];
let r_var = env.subs[r];
outcome.union(unify_pool(env, pool, l_var, r_var, ctx.mode));
let merged_var = choose_merged_var(env.subs, l_var, r_var);
merged_infer_ext_in_output_vars.push(merged_var);
}
if outcome.mismatches.is_empty() {
// Even if there are no changes to alias arguments, and no new variables were
// introduced, we may still need to unify the "actual types" of the alias or opaque!
//
// The unification is not necessary from a types perspective (and in fact, we may want
// to disable it for `roc check` later on), but it is necessary for the monomorphizer,
// which expects identical types to be reflected in the same variable.
//
// As a concrete example, consider the unification of two opaques
//
// P := [Zero, Succ P]
//
// (@P (Succ n)) ~ (@P (Succ o))
//
// `P` has no arguments, and unification of the surface of `P` introduces nothing new.
// But if we do not unify the types of `n` and `o`, which are recursion variables, they
// will remain disjoint! Currently, the implication of this is that they will be seen
// to have separate recursive memory layouts in the monomorphizer - which is no good
// for our compilation model.
//
// As such, always unify the real vars.
// Don't report real_var mismatches, because they must always be surfaced higher, from
// the argument types.
let mut real_var_outcome =
unify_pool::<M>(env, pool, real_var, other_real_var, ctx.mode);
let _ = real_var_outcome.mismatches.drain(..);
outcome.union(real_var_outcome);
let merged_real_var = choose_merged_var(env.subs, real_var, other_real_var);
// POSSIBLE OPT: choose_merged_var chooses the left when the choice is arbitrary. If
// the merged vars are all left, avoid re-insertion. Is checking for argument slice
// equality faster than re-inserting?
let merged_variables = AliasVariables::insert_into_subs(
env.subs,
merged_args,
merged_lambda_set_args,
merged_infer_ext_in_output_vars,
);
let merged_content = Content::Alias(symbol, merged_variables, merged_real_var, kind);
outcome.union(merge(env, ctx, merged_content));
}
outcome
} else {
mismatch!("{:?}", symbol)
}
}
// Unifies a structural alias
#[inline(always)]
#[must_use]
fn unify_alias<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
symbol: Symbol,
args: AliasVariables,
real_var: Variable,
) -> Outcome<M> {
let other_content = &ctx.second_desc.content;
let kind = AliasKind::Structural;
match other_content {
FlexVar(_) => {
// Alias wins
merge(env, ctx, Alias(symbol, args, real_var, kind))
}
RecursionVar { structure, .. } => unify_pool(env, pool, real_var, *structure, ctx.mode),
RigidVar(_) | RigidAbleVar(..) | FlexAbleVar(..) => {
unify_pool(env, pool, real_var, ctx.second, ctx.mode)
}
Alias(_, _, _, AliasKind::Opaque) => unify_pool(env, pool, real_var, ctx.second, ctx.mode),
Alias(other_symbol, other_args, other_real_var, AliasKind::Structural) => {
if symbol == *other_symbol {
unify_two_aliases(
env,
pool,
ctx,
AliasKind::Structural,
symbol,
args,
real_var,
*other_args,
*other_real_var,
)
} else {
unify_pool(env, pool, real_var, *other_real_var, ctx.mode)
}
}
Structure(_) => unify_pool(env, pool, real_var, ctx.second, ctx.mode),
RangedNumber(other_range_vars) => {
check_and_merge_valid_range(env, pool, ctx, ctx.second, *other_range_vars, ctx.first)
}
LambdaSet(..) => mismatch!("cannot unify alias {:?} with lambda set {:?}: lambda sets should never be directly behind an alias!", ctx.first, other_content),
Error => merge(env, ctx, Error),
}
}
#[inline(always)]
fn opaque_obligation(opaque: Symbol, opaque_var: Variable) -> Obligated {
match opaque.module_id() {
// Numbers should be treated as ad-hoc obligations for ability checking.
ModuleId::NUM => Obligated::Adhoc(opaque_var),
_ => Obligated::Opaque(opaque),
}
}
#[inline(always)]
#[must_use]
fn unify_opaque<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
symbol: Symbol,
args: AliasVariables,
real_var: Variable,
) -> Outcome<M> {
let other_content = &ctx.second_desc.content;
let kind = AliasKind::Opaque;
match other_content {
FlexVar(_) => {
// Alias wins
merge(env, ctx, Alias(symbol, args, real_var, kind))
}
FlexAbleVar(_, abilities) => {
// Opaque type wins
merge_flex_able_with_concrete(
env,
ctx,
ctx.second,
*abilities,
Alias(symbol, args, real_var, kind),
opaque_obligation(symbol, ctx.first),
)
}
Alias(_, _, other_real_var, AliasKind::Structural) => {
unify_pool(env, pool, ctx.first, *other_real_var, ctx.mode)
}
RecursionVar { structure, .. } => unify_pool(env, pool, ctx.first, *structure, ctx.mode),
Alias(other_symbol, other_args, other_real_var, AliasKind::Opaque) => {
// Opaques types are only equal if the opaque symbols are equal!
if symbol == *other_symbol {
unify_two_aliases(
env,
pool,
ctx,
AliasKind::Opaque,
symbol,
args,
real_var,
*other_args,
*other_real_var,
)
} else {
mismatch!("{:?}", symbol)
}
}
RangedNumber(other_range_vars) => {
// This opaque might be a number, check if it unifies with the target ranged number var.
check_and_merge_valid_range(env, pool, ctx, ctx.second, *other_range_vars, ctx.first)
}
Error => merge(env, ctx, Error),
// _other has an underscore because it's unused in --release builds
_other => {
// The type on the left is an opaque, but the one on the right is not!
mismatch!("Cannot unify opaque {:?} with {:?}", symbol, _other)
}
}
}
#[inline(always)]
#[must_use]
fn unify_structure<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
flat_type: &FlatType,
other: &Content,
) -> Outcome<M> {
match other {
FlexVar(_) => {
// If the other is flex, Structure wins!
merge(env, ctx, Structure(*flat_type))
}
FlexAbleVar(_, abilities) => {
// Structure wins
merge_flex_able_with_concrete(
env,
ctx,
ctx.second,
*abilities,
Structure(*flat_type),
Obligated::Adhoc(ctx.first),
)
}
// _name has an underscore because it's unused in --release builds
RigidVar(_name) => {
// Type mismatch! Rigid can only unify with flex.
mismatch!(
"trying to unify {:?} with rigid var {:?}",
&flat_type,
_name
)
}
RigidAbleVar(_, _abilities) => {
mismatch!(
%not_able, ctx.first, env.subs.get_subs_slice(*_abilities),
"trying to unify {:?} with RigidAble {:?}",
&flat_type,
&other
)
}
RecursionVar { structure, .. } => match flat_type {
FlatType::TagUnion(_, _) => {
// unify the structure with this unrecursive tag union
unify_pool(env, pool, ctx.first, *structure, ctx.mode)
}
FlatType::RecursiveTagUnion(rec, _, _) => {
debug_assert!(is_recursion_var(env.subs, *rec));
// unify the structure with this recursive tag union
unify_pool(env, pool, ctx.first, *structure, ctx.mode)
}
FlatType::FunctionOrTagUnion(_, _, _) => {
// unify the structure with this unrecursive tag union
unify_pool(env, pool, ctx.first, *structure, ctx.mode)
}
// Only tag unions can be recursive; everything else is an error.
_ => mismatch!(
"trying to unify {:?} with recursive type var {:?}",
&flat_type,
structure
),
},
Structure(ref other_flat_type) => {
// Unify the two flat types
unify_flat_type(env, pool, ctx, flat_type, other_flat_type)
}
// _sym has an underscore because it's unused in --release builds
Alias(_sym, _, real_var, kind) => match kind {
AliasKind::Structural => {
// NB: not treating this as a presence constraint seems pivotal! I
// can't quite figure out why, but it doesn't seem to impact other types.
unify_pool(env, pool, ctx.first, *real_var, ctx.mode.as_eq())
}
AliasKind::Opaque => {
mismatch!(
"Cannot unify structure {:?} with opaque {:?}",
&flat_type,
_sym
)
}
},
LambdaSet(..) => {
mismatch!(
"Cannot unify structure \n{:?} \nwith lambda set\n {:?}",
roc_types::subs::SubsFmtContent(&Content::Structure(*flat_type), env.subs),
roc_types::subs::SubsFmtContent(other, env.subs),
// &flat_type,
// other
)
}
RangedNumber(other_range_vars) => {
check_and_merge_valid_range(env, pool, ctx, ctx.second, *other_range_vars, ctx.first)
}
Error => merge(env, ctx, Error),
}
}
#[inline(always)]
#[must_use]
fn unify_lambda_set<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
lambda_set: LambdaSet,
other: &Content,
) -> Outcome<M> {
match other {
FlexVar(_) => {
if M::UNIFYING_SPECIALIZATION {
// TODO: It appears that this can happen in well-typed, reasonable programs, but it's
// open question as to why! See also https://github.com/roc-lang/roc/issues/3163.
let zero_lambda_set = LambdaSet {
solved: UnionLabels::default(),
recursion_var: OptVariable::NONE,
unspecialized: SubsSlice::default(),
ambient_function: env.subs.fresh_unnamed_flex_var(),
};
extract_specialization_lambda_set(env, ctx, lambda_set, zero_lambda_set)
} else {
merge(env, ctx, Content::LambdaSet(lambda_set))
}
}
Content::LambdaSet(other_lambda_set) => {
if M::UNIFYING_SPECIALIZATION {
extract_specialization_lambda_set(env, ctx, lambda_set, *other_lambda_set)
} else {
unify_lambda_set_help(env, pool, ctx, lambda_set, *other_lambda_set)
}
}
RecursionVar { structure, .. } => {
// suppose that the recursion var is a lambda set
unify_pool(env, pool, ctx.first, *structure, ctx.mode)
}
RigidVar(..) | RigidAbleVar(..) => mismatch!("Lambda sets never unify with rigid"),
FlexAbleVar(..) => mismatch!("Lambda sets should never have abilities attached to them"),
Structure(..) => mismatch!("Lambda set cannot unify with non-lambda set structure"),
RangedNumber(..) => mismatch!("Lambda sets are never numbers"),
Alias(..) => mismatch!("Lambda set can never be directly under an alias!"),
Error => merge(env, ctx, Error),
}
}
fn extract_specialization_lambda_set<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
ability_member_proto_lset: LambdaSet,
specialization_lset: LambdaSet,
) -> Outcome<M> {
// We should have the unspecialized ability member lambda set on the left and the
// specialization lambda set on the right. E.g.
//
// [[] + a:toEncoder:1] ~ [[myTypeLset]]
//
// Taking that example, we keep around [[myTypeLset]] in the unification and associate
// (toEncoder, 1) => [[myTypeLset]] in the metadata collector.
let LambdaSet {
solved: member_solved,
recursion_var: member_rec_var,
unspecialized: member_uls_slice,
ambient_function: _,
} = ability_member_proto_lset;
debug_assert!(
member_solved.is_empty(),
"member signature should not have solved lambda sets"
);
debug_assert!(member_rec_var.is_none());
let member_uls = env.subs.get_subs_slice(member_uls_slice);
debug_assert_eq!(
member_uls.len(),
1,
"member signature lambda sets should contain only one unspecialized lambda set"
);
let Uls(_, member, region) = member_uls[0];
let mut outcome: Outcome<M> = merge(env, ctx, Content::LambdaSet(specialization_lset));
outcome
.extra_metadata
.record_specialization_lambda_set(member, region, ctx.second);
outcome
}
#[derive(Debug)]
struct Sides {
left: Vec<(Symbol, VariableSubsSlice)>,
right: Vec<(Symbol, VariableSubsSlice)>,
}
impl Default for Sides {
fn default() -> Self {
Self {
left: Vec::with_capacity(1),
right: Vec::with_capacity(1),
}
}
}
struct SeparatedUnionLambdas {
only_in_left: Vec<(Symbol, VariableSubsSlice)>,
only_in_right: Vec<(Symbol, VariableSubsSlice)>,
joined: Vec<(Symbol, VariableSubsSlice)>,
}
fn separate_union_lambdas<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
mode: Mode,
fields1: UnionLambdas,
fields2: UnionLambdas,
) -> (Outcome<M>, SeparatedUnionLambdas) {
debug_assert!(
fields1.is_sorted_allow_duplicates(env.subs),
"not sorted: {:?}",
fields1.iter_from_subs(env.subs).collect::<Vec<_>>()
);
debug_assert!(
fields2.is_sorted_allow_duplicates(env.subs),
"not sorted: {:?}",
fields2.iter_from_subs(env.subs).collect::<Vec<_>>()
);
// lambda names -> (the captures for that lambda on the left side, the captures for that lambda on the right side)
// e.g. [[F1 U8], [F1 U64], [F2 a]] ~ [[F1 Str], [F2 Str]] becomes
// F1 -> { left: [ [U8], [U64] ], right: [ [Str] ] }
// F2 -> { left: [ [a] ], right: [ [Str] ] }
let mut buckets: VecMap<Symbol, Sides> = VecMap::with_capacity(fields1.len() + fields2.len());
let (mut fields_left, mut fields_right) = (
fields1.iter_all().into_iter().peekable(),
fields2.iter_all().into_iter().peekable(),
);
loop {
use std::cmp::Ordering;
let ord = match (fields_left.peek(), fields_right.peek()) {
(Some((l, _)), Some((r, _))) => Some((env.subs[*l]).cmp(&env.subs[*r])),
(Some(_), None) => Some(Ordering::Less),
(None, Some(_)) => Some(Ordering::Greater),
(None, None) => None,
};
match ord {
Some(Ordering::Less) => {
let (sym, vars) = fields_left.next().unwrap();
let bucket = buckets.get_or_insert(env.subs[sym], Sides::default);
bucket.left.push((env.subs[sym], env.subs[vars]));
}
Some(Ordering::Greater) => {
let (sym, vars) = fields_right.next().unwrap();
let bucket = buckets.get_or_insert(env.subs[sym], Sides::default);
bucket.right.push((env.subs[sym], env.subs[vars]));
}
Some(Ordering::Equal) => {
let (sym, left_vars) = fields_left.next().unwrap();
let (_sym, right_vars) = fields_right.next().unwrap();
debug_assert_eq!(env.subs[sym], env.subs[_sym]);
let bucket = buckets.get_or_insert(env.subs[sym], Sides::default);
bucket.left.push((env.subs[sym], env.subs[left_vars]));
bucket.right.push((env.subs[sym], env.subs[right_vars]));
}
None => break,
}
}
let mut whole_outcome = Outcome::default();
let mut only_in_left = Vec::with_capacity(fields1.len());
let mut only_in_right = Vec::with_capacity(fields2.len());
let mut joined = Vec::with_capacity(fields1.len() + fields2.len());
for (lambda_name, Sides { left, mut right }) in buckets {
match (left.as_slice(), right.as_slice()) {
(&[], &[]) => internal_error!("somehow both are empty but there's an entry?"),
(&[], _) => only_in_right.extend(right),
(_, &[]) => only_in_left.extend(left),
(_, _) => {
'next_left: for (_, left_slice) in left {
// Does the current slice on the left unify with a slice on the right?
//
// If yes, we unify then and the unified result to `joined`.
//
// Otherwise if no such slice on the right is found, then the slice on the `left` has no slice,
// either on the left or right, it unifies with (since the left was constructed
// inductively via the same procedure).
//
// At the end each slice in the left and right has been explored, so
// - `joined` contains all the slices that can unify
// - left contains unique captures slices that will unify with no other slice
// - right contains unique captures slices that will unify with no other slice
//
// Note also if a slice l on the left and a slice r on the right unify, there
// is no other r' != r on the right such that l ~ r', and respectively there is
// no other l' != l on the left such that l' ~ r. Otherwise, it must be that l ~ l'
// (resp. r ~ r'), but then l = l' (resp. r = r'), and they would have become the same
// slice in a previous call to `separate_union_lambdas`.
'try_next_right: for (right_index, (_, right_slice)) in right.iter().enumerate()
{
if left_slice.len() != right_slice.len() {
continue 'try_next_right;
}
for (var1, var2) in (left_slice.into_iter()).zip(right_slice.into_iter()) {
let (var1, var2) = (env.subs[var1], env.subs[var2]);
if M::IS_LATE {
// Lambda sets are effectively tags under another name, and their usage can also result
// in the arguments of a lambda name being recursive. It very well may happen that
// during unification, a lambda set previously marked as not recursive becomes
// recursive. See the docs of [LambdaSet] for one example, or https://github.com/roc-lang/roc/pull/2307.
//
// Like with tag unions, if it is, we'll always pass through this branch. So, take
// this opportunity to promote the lambda set to recursive if need be.
//
// THEORY: observe that this check only happens in late unification
// (i.e. code generation, typically). We believe this to be correct
// because while recursive lambda sets must be collapsed to the
// code generator, we have not yet observed a case where they must
// collapsed to the type checker of the surface syntax.
// It is possible this assumption will be invalidated!
maybe_mark_union_recursive(env, var1);
maybe_mark_union_recursive(env, var2);
}
// Check whether the two type variables in the closure set are
// unifiable. If they are, we can unify them and continue on assuming
// that these lambdas are in fact the same.
//
// If they are not unifiable, that means the two lambdas must be
// different (since they have different capture sets), and so we don't
// want to merge the variables.
let variables_are_unifiable = env.with_outcome_only(|env| {
unify_pool::<NoCollector>(env, pool, var1, var2, mode)
.mismatches
.is_empty()
});
if !variables_are_unifiable {
continue 'try_next_right;
}
let outcome = unify_pool(env, pool, var1, var2, mode);
whole_outcome.union(outcome);
}
// All the variables unified, so we can join the left + right.
// The variables are unified in left and right slice, so just reuse the left slice.
joined.push((lambda_name, left_slice));
// Remove the right slice, it unifies with the left so this is its unique
// unification.
// Remove in-place so that the order is preserved.
right.remove(right_index);
continue 'next_left;
}
// No slice on the right unified with the left, so the slice on the left is on
// its own.
only_in_left.push((lambda_name, left_slice));
}
// Possible that there are items left over in the right, they are on their own.
only_in_right.extend(right);
}
}
}
(
whole_outcome,
SeparatedUnionLambdas {
only_in_left,
only_in_right,
joined,
},
)
}
/// ULS-SORT-ORDER:
/// - Arrange into partitions of (_, member, region), in ascending order of (member, region).
/// - Within each partition, place flex-able vars at the end of the partition.
/// - Amongst all flex-able vars, sort by their root key, so that identical vars are next to each other.
#[inline(always)]
fn unspecialized_lambda_set_sorter(subs: &Subs, uls1: Uls, uls2: Uls) -> std::cmp::Ordering {
let Uls(var1, sym1, region1) = uls1;
let Uls(var2, sym2, region2) = uls2;
use std::cmp::Ordering::*;
use Content::*;
match (sym1, region1).cmp(&(sym2, region2)) {
Equal => {
match (
subs.get_content_without_compacting(var1),
subs.get_content_without_compacting(var2),
) {
(FlexAbleVar(..) | RigidAbleVar(..), FlexAbleVar(..) | RigidAbleVar(..)) => subs
.get_root_key_without_compacting(var1)
.cmp(&subs.get_root_key_without_compacting(var2)),
(FlexVar(..) | RigidVar(..), _) | (_, FlexVar(..) | RigidVar(..)) => {
internal_error!("unexpected variable type in unspecialized lambda set!")
}
(FlexAbleVar(..), _) => Greater,
(_, FlexAbleVar(..)) => Less,
// For everything else, the order is irrelevant
(_, _) => Less,
}
}
ord => ord,
}
}
#[inline(always)]
fn sort_unspecialized_lambda_sets(subs: &Subs, mut uls: Vec<Uls>) -> Vec<Uls> {
uls.sort_by(|&uls1, &uls2| unspecialized_lambda_set_sorter(subs, uls1, uls2));
uls
}
#[inline(always)]
fn is_sorted_unspecialized_lamba_set_list(subs: &Subs, uls: &[Uls]) -> bool {
uls == sort_unspecialized_lambda_sets(subs, uls.to_vec())
}
#[must_use = "must use outcomes!"]
fn unify_unspecialized_lambdas<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
mode: Mode,
uls_left: SubsSlice<Uls>,
uls_right: SubsSlice<Uls>,
) -> Result<(SubsSlice<Uls>, Outcome<M>), Outcome<M>> {
// Note that we don't need to update the bookkeeping of variable -> lambda set to be resolved,
// because if we had v1 -> lset1, and now lset1 ~ lset2, then afterward either lset1 still
// resolves to itself or re-points to lset2.
// In either case the merged unspecialized lambda sets will be there.
let (uls_left, uls_right) = match (uls_left.is_empty(), uls_right.is_empty()) {
(true, true) => return Ok((SubsSlice::default(), Default::default())),
(false, true) => return Ok((uls_left, Default::default())),
(true, false) => return Ok((uls_right, Default::default())),
(false, false) => (
env.subs.get_subs_slice(uls_left).to_vec(),
env.subs.get_subs_slice(uls_right).to_vec(),
),
};
// Unfortunately, it is not an invariant that `uls_left` and `uls_right` obey ULS-SORT-ORDER before
// merging.
//
// That's because flex-able variables in unspecialized lambda sets may be unified at any time,
// and unification of flex-able variables may change their root keys, which ULS-SORT-ORDER
// considers.
//
// As such, we must sort beforehand. In practice these sets are very, very small (<5 elements).
let uls_left = sort_unspecialized_lambda_sets(env.subs, uls_left);
let uls_right = sort_unspecialized_lambda_sets(env.subs, uls_right);
let (mut uls_left, mut uls_right) = (uls_left.iter().peekable(), uls_right.iter().peekable());
let mut merged_uls = Vec::with_capacity(uls_left.len() + uls_right.len());
let mut whole_outcome = Outcome::default();
loop {
let (uls_l, uls_r) = match (uls_left.peek(), uls_right.peek()) {
(Some(uls_l), Some(uls_r)) => (**uls_l, **uls_r),
(Some(_), None) => {
merged_uls.push(*uls_left.next().unwrap());
continue;
}
(None, Some(_)) => {
merged_uls.push(*uls_right.next().unwrap());
continue;
}
(None, None) => break,
};
let Uls(var_l, sym_l, region_l) = uls_l;
let Uls(var_r, sym_r, region_r) = uls_r;
use std::cmp::Ordering::*;
match (sym_l, region_l).cmp(&(sym_r, region_r)) {
Less => {
// Left needs to catch up to right, add it to the merged lambdas.
merged_uls.push(*uls_left.next().unwrap());
}
Greater => {
// Right needs to catch up to left, add it to the merged lambdas.
merged_uls.push(*uls_right.next().unwrap());
}
Equal => {
// The interesting case - both point to the same specialization.
use Content::*;
match (
env.subs.get_content_without_compacting(var_l),
env.subs.get_content_without_compacting(var_r),
) {
(FlexAbleVar(..) | RigidAbleVar(..), FlexAbleVar(..) | RigidAbleVar(..)) => {
// If the types are root-equivalent, de-duplicate them.
//
// Otherwise, the type variables are disjoint, and we want to keep both
// of them, for purposes of disjoint variable lambda specialization.
//
// For more information, see "A Property thats lost, and how we can hold on to it"
// in solve/docs/ambient_lambda_set_specialization.md.
if env.subs.equivalent_without_compacting(var_l, var_r) {
// ... a1 ...
// ... b1=a1 ...
// => ... a1 ...
//
// Keep the one on the left, drop the one on the right.
//
// Then progress both, because the invariant tells us they must be
// disjoint, and if there were any concrete variables, they would have
// appeared earlier.
let _dropped = uls_right.next().unwrap();
let kept = uls_left.next().unwrap();
merged_uls.push(*kept);
} else if mode.is_lambda_set_specialization() {
// ... a1 ...
// ... b1 ...
// => ... a1=b1 ...
//
// If we're in the process of running the ambient lambda set
// specialization procedure, disjoint type variables being merged from
// the left and right lists are treated specially!
//
// In particular, we are unifying a local list of lambda sets, for
// which the specialization is for (on the left), with specialization
// lambda sets, which have just been freshened (on the right).
//
// [ .. a:lam:1 ] (local, undergoing specialization)
// [ .. a':lam:1 ] (specialization lambda sets, just freshened)
//
// Because the specialization lambdas are freshened, they certainly are
// disjoint from the local lambdas - but they may be equivalent in
// principle, from the perspective of a human looking at the
// unification!
//
// Running with the example above, the specialization lambda set has an
// unspecialized lambda `a':lam:1`. Now, this is disjoint from
// `a:lam:1` in the local lambda set, from the purely technical
// perspective that `a' != a`.
//
// But, in expected function, they **should not** be treated as disjoint!
// In this case, the specialization lambda is not introducing any new
// information, and is targeting exactly the local lambda `a:lam:1`.
//
// So, to avoid introducing superfluous variables, we unify these disjoint
// variables once, and then progress on both sides. We progress on both
// sides to avoid unifying more than what we should in our principle.
//
// It doesn't matter which side we choose to progress on, since after
// unification of flex vars roots are equivalent. So, choose the left
// side.
//
// See the ambient lambda set specialization document for more details.
let outcome = unify_pool(env, pool, var_l, var_r, mode);
if !outcome.mismatches.is_empty() {
return Err(outcome);
}
whole_outcome.union(outcome);
debug_assert!(env.subs.equivalent_without_compacting(var_l, var_r));
let _dropped = uls_right.next().unwrap();
let kept = uls_left.next().unwrap();
merged_uls.push(*kept);
} else {
// ... a1 ...
// ... b1 ...
// => ... a1, b1 ...
//
// Keep both. But, we have to be careful about how we do this -
// immediately add the one with the lower root, and advance that side;
// keep the other as-is, because the next variable on the advanced side
// might be lower than the current non-advanced variable. For example:
//
// ... 640 645 ...
// ... 670 ...
//
// we want to add `640` to the merged list and advance to
//
// ... 645 ...
// ... 670 ...
//
// rather than adding both `640` and `670`, and skipping the comparison
// of `645` with `670`.
//
// An important thing to notice is that we *don't* want to advance
// both sides, because if these two variables are disjoint, then
// advancing one side *might* make the next comparison be between
// equivalent variables, for example in a case like
//
// ... 640 670 ...
// ... 670 ...
//
// In the above case, we certainly only want to advance the left side!
if env.subs.get_root_key(var_l) < env.subs.get_root_key(var_r) {
let kept = uls_left.next().unwrap();
merged_uls.push(*kept);
} else {
let kept = uls_right.next().unwrap();
merged_uls.push(*kept);
}
}
}
(FlexAbleVar(..) | RigidAbleVar(..), _) => {
// ... a1 ...
// ... {foo: _} ...
// => ... {foo: _} ...
//
// Unify them, then advance the merged flex var.
let outcome = unify_pool(env, pool, var_l, var_r, mode);
if !outcome.mismatches.is_empty() {
return Err(outcome);
}
whole_outcome.union(outcome);
let _dropped = uls_right.next().unwrap();
}
(_, FlexAbleVar(..) | RigidAbleVar(..)) => {
// ... {foo: _} ...
// ... a1 ...
// => ... {foo: _} ...
//
// Unify them, then advance the merged flex var.
let outcome = unify_pool(env, pool, var_l, var_r, mode);
if !outcome.mismatches.is_empty() {
return Err(outcome);
}
whole_outcome.union(outcome);
let _dropped = uls_left.next().unwrap();
}
(_, _) => {
// ... {foo: _} ...
// ... {foo: _} ...
// => ... {foo: _} ...
//
// Unify them, then advance one.
// (the choice is arbitrary, so we choose the left)
let outcome = unify_pool(env, pool, var_l, var_r, mode);
if !outcome.mismatches.is_empty() {
return Err(outcome);
}
whole_outcome.union(outcome);
let _dropped = uls_left.next().unwrap();
}
}
}
}
}
debug_assert!(
is_sorted_unspecialized_lamba_set_list(env.subs, &merged_uls),
"merging of unspecialized lambda sets does not preserve sort! {:?}",
merged_uls
);
Ok((
SubsSlice::extend_new(&mut env.subs.unspecialized_lambda_sets, merged_uls),
whole_outcome,
))
}
#[must_use]
fn unify_lambda_set_help<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
lset1: self::LambdaSet,
lset2: self::LambdaSet,
) -> Outcome<M> {
// LambdaSets unify like TagUnions, but can grow unbounded regardless of the extension
// variable.
let LambdaSet {
solved: solved1,
recursion_var: rec1,
unspecialized: uls1,
ambient_function: ambient_function_var1,
} = lset1;
let LambdaSet {
solved: solved2,
recursion_var: rec2,
unspecialized: uls2,
ambient_function: ambient_function_var2,
} = lset2;
// Assumed precondition: the ambient functions have already been unified, or are in the process
// of being unified - otherwise, how could we have reached unification of lambda sets?
let _ = ambient_function_var2;
let ambient_function_var = ambient_function_var1;
debug_assert!(
(rec1.into_variable().into_iter())
.chain(rec2.into_variable().into_iter())
.all(|v| is_recursion_var(env.subs, v)),
"Recursion var is present, but it doesn't have a recursive content!"
);
let (
mut whole_outcome,
SeparatedUnionLambdas {
only_in_left,
only_in_right,
joined,
},
) = separate_union_lambdas(env, pool, ctx.mode, solved1, solved2);
let all_lambdas = joined
.into_iter()
.map(|(name, slice)| (name, env.subs.get_subs_slice(slice).to_vec()));
let all_lambdas = merge_sorted_preserving_duplicates(
all_lambdas,
only_in_left.into_iter().map(|(name, subs_slice)| {
let vec = env.subs.get_subs_slice(subs_slice).to_vec();
(name, vec)
}),
);
let all_lambdas = merge_sorted_preserving_duplicates(
all_lambdas,
only_in_right.into_iter().map(|(name, subs_slice)| {
let vec = env.subs.get_subs_slice(subs_slice).to_vec();
(name, vec)
}),
);
let recursion_var = match (rec1.into_variable(), rec2.into_variable()) {
// Prefer left when it's available.
(Some(rec), _) | (_, Some(rec)) => OptVariable::from(rec),
(None, None) => OptVariable::NONE,
};
let merged_unspecialized = match unify_unspecialized_lambdas(env, pool, ctx.mode, uls1, uls2) {
Ok((merged, outcome)) => {
whole_outcome.union(outcome);
merged
}
Err(outcome) => {
debug_assert!(!outcome.mismatches.is_empty());
return outcome;
}
};
let new_solved = UnionLabels::insert_into_subs(env.subs, all_lambdas);
let new_lambda_set = Content::LambdaSet(LambdaSet {
solved: new_solved,
recursion_var,
unspecialized: merged_unspecialized,
ambient_function: ambient_function_var,
});
let merge_outcome = merge(env, ctx, new_lambda_set);
whole_outcome.union(merge_outcome);
whole_outcome
}
#[must_use]
fn unify_record<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
fields1: RecordFields,
ext1: Variable,
fields2: RecordFields,
ext2: Variable,
) -> Outcome<M> {
let subs = &mut env.subs;
let (separate, ext1, ext2) = separate_record_fields(subs, fields1, ext1, fields2, ext2);
let shared_fields = separate.in_both;
if separate.only_in_1.is_empty() {
if separate.only_in_2.is_empty() {
// these variable will be the empty record, but we must still unify them
let ext_outcome = unify_pool(env, pool, ext1, ext2, ctx.mode);
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
let mut field_outcome =
unify_shared_fields(env, pool, ctx, shared_fields, OtherFields::None, ext1);
field_outcome.union(ext_outcome);
field_outcome
} else {
let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2);
let flat_type = FlatType::Record(only_in_2, ext2);
let sub_record = fresh(env, pool, ctx, Structure(flat_type));
let ext_outcome = unify_pool(env, pool, ext1, sub_record, ctx.mode);
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
let mut field_outcome =
unify_shared_fields(env, pool, ctx, shared_fields, OtherFields::None, sub_record);
field_outcome.union(ext_outcome);
field_outcome
}
} else if separate.only_in_2.is_empty() {
let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1);
let flat_type = FlatType::Record(only_in_1, ext1);
let sub_record = fresh(env, pool, ctx, Structure(flat_type));
let ext_outcome = unify_pool(env, pool, sub_record, ext2, ctx.mode);
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
let mut field_outcome =
unify_shared_fields(env, pool, ctx, shared_fields, OtherFields::None, sub_record);
field_outcome.union(ext_outcome);
field_outcome
} else {
let only_in_1 = RecordFields::insert_into_subs(subs, separate.only_in_1);
let only_in_2 = RecordFields::insert_into_subs(subs, separate.only_in_2);
let other_fields = OtherFields::Other(only_in_1, only_in_2);
let ext = fresh(env, pool, ctx, Content::FlexVar(None));
let flat_type1 = FlatType::Record(only_in_1, ext);
let flat_type2 = FlatType::Record(only_in_2, ext);
let sub1 = fresh(env, pool, ctx, Structure(flat_type1));
let sub2 = fresh(env, pool, ctx, Structure(flat_type2));
let rec1_outcome = unify_pool(env, pool, ext1, sub2, ctx.mode);
if !rec1_outcome.mismatches.is_empty() {
return rec1_outcome;
}
let rec2_outcome = unify_pool(env, pool, sub1, ext2, ctx.mode);
if !rec2_outcome.mismatches.is_empty() {
return rec2_outcome;
}
let mut field_outcome =
unify_shared_fields(env, pool, ctx, shared_fields, other_fields, ext);
field_outcome
.mismatches
.reserve(rec1_outcome.mismatches.len() + rec2_outcome.mismatches.len());
field_outcome.union(rec1_outcome);
field_outcome.union(rec2_outcome);
field_outcome
}
}
enum OtherFields {
None,
Other(RecordFields, RecordFields),
}
type SharedFields = Vec<(Lowercase, (RecordField<Variable>, RecordField<Variable>))>;
#[must_use]
fn unify_shared_fields<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
shared_fields: SharedFields,
other_fields: OtherFields,
ext: Variable,
) -> Outcome<M> {
let mut matching_fields = Vec::with_capacity(shared_fields.len());
let num_shared_fields = shared_fields.len();
let mut whole_outcome = Outcome::default();
for (name, (actual, expected)) in shared_fields {
let local_outcome = unify_pool(
env,
pool,
actual.into_inner(),
expected.into_inner(),
ctx.mode,
);
if local_outcome.mismatches.is_empty() {
use RecordField::*;
// Unification of optional fields
//
// Demanded does not unify with Optional
// RigidOptional does not unify with Required or Demanded
// RigidRequired does not unify with Optional
// Unifying Required with Demanded => Demanded
// Unifying Optional with Required => Required
// Unifying Optional with RigidOptional => RigidOptional
// Unifying X with X => X
let actual = match (actual, expected) {
(Demanded(_), Optional(_)) | (Optional(_), Demanded(_)) => {
// this is an error, but we continue to give better error messages
continue;
}
(Demanded(val), Required(_))
| (Required(val), Demanded(_))
| (Demanded(val), Demanded(_)) => Demanded(val),
(Required(val), Required(_)) => Required(val),
(Required(val), Optional(_)) => Required(val),
(Optional(val), Required(_)) => Required(val),
(Optional(val), Optional(_)) => Optional(val),
// rigid optional
(RigidOptional(val), Optional(_)) | (Optional(_), RigidOptional(val)) => {
RigidOptional(val)
}
(RigidOptional(_), Demanded(_) | Required(_) | RigidRequired(_))
| (Demanded(_) | Required(_) | RigidRequired(_), RigidOptional(_)) => {
// this is an error, but we continue to give better error messages
continue;
}
(RigidOptional(val), RigidOptional(_)) => RigidOptional(val),
// rigid required
(RigidRequired(_), Optional(_)) | (Optional(_), RigidRequired(_)) => {
// this is an error, but we continue to give better error messages
continue;
}
(RigidRequired(val), Demanded(_) | Required(_))
| (Demanded(_) | Required(_), RigidRequired(val)) => RigidRequired(val),
(RigidRequired(val), RigidRequired(_)) => RigidRequired(val),
};
matching_fields.push((name, actual));
whole_outcome.union(local_outcome);
}
}
if num_shared_fields == matching_fields.len() {
// pull fields in from the ext_var
let (ext_fields, new_ext_var) =
RecordFields::empty().sorted_iterator_and_ext(env.subs, ext);
let ext_fields: Vec<_> = ext_fields.into_iter().collect();
let fields: RecordFields = match other_fields {
OtherFields::None => {
if ext_fields.is_empty() {
RecordFields::insert_into_subs(env.subs, matching_fields)
} else {
let all_fields = merge_sorted(matching_fields, ext_fields);
RecordFields::insert_into_subs(env.subs, all_fields)
}
}
OtherFields::Other(other1, other2) => {
let mut all_fields = merge_sorted(matching_fields, ext_fields);
all_fields = merge_sorted(
all_fields,
other1.iter_all().map(|(i1, i2, i3)| {
let field_name: Lowercase = env.subs[i1].clone();
let variable = env.subs[i2];
let record_field: RecordField<Variable> = env.subs[i3].map(|_| variable);
(field_name, record_field)
}),
);
all_fields = merge_sorted(
all_fields,
other2.iter_all().map(|(i1, i2, i3)| {
let field_name: Lowercase = env.subs[i1].clone();
let variable = env.subs[i2];
let record_field: RecordField<Variable> = env.subs[i3].map(|_| variable);
(field_name, record_field)
}),
);
RecordFields::insert_into_subs(env.subs, all_fields)
}
};
let flat_type = FlatType::Record(fields, new_ext_var);
let merge_outcome = merge(env, ctx, Structure(flat_type));
whole_outcome.union(merge_outcome);
whole_outcome
} else {
mismatch!("in unify_shared_fields")
}
}
fn separate_record_fields(
subs: &Subs,
fields1: RecordFields,
ext1: Variable,
fields2: RecordFields,
ext2: Variable,
) -> (
Separate<Lowercase, RecordField<Variable>>,
Variable,
Variable,
) {
let (it1, new_ext1) = fields1.sorted_iterator_and_ext(subs, ext1);
let (it2, new_ext2) = fields2.sorted_iterator_and_ext(subs, ext2);
let it1 = it1.collect::<Vec<_>>();
let it2 = it2.collect::<Vec<_>>();
(separate(it1, it2), new_ext1, new_ext2)
}
// TODO: consider combining with `merge_sorted_help` with a `by_key` predicate.
// But that might not get inlined!
fn merge_sorted_keys<K, I1, I2>(input1: I1, input2: I2) -> Vec<K>
where
K: Ord,
I1: ExactSizeIterator<Item = K>,
I2: ExactSizeIterator<Item = K>,
{
use std::cmp::Ordering::{Equal, Greater, Less};
let mut merged = Vec::with_capacity(input1.len() + input2.len());
let mut input1 = input1.peekable();
let mut input2 = input2.peekable();
loop {
let choice = match (input1.peek(), input2.peek()) {
(Some(l), Some(r)) => l.cmp(r),
(Some(_), None) => Less,
(None, Some(_)) => Greater,
(None, None) => break,
};
match choice {
Less => {
merged.push(input1.next().unwrap());
}
Greater => {
merged.push(input2.next().unwrap());
}
Equal => {
let k = input1.next().unwrap();
let _ = input2.next().unwrap();
merged.push(k)
}
}
}
merged
}
#[derive(Debug)]
struct Separate<K, V> {
only_in_1: Vec<(K, V)>,
only_in_2: Vec<(K, V)>,
in_both: Vec<(K, (V, V))>,
}
fn merge_sorted_help<K, V, I1, I2>(input1: I1, input2: I2, preserve_duplicates: bool) -> Vec<(K, V)>
where
K: Ord,
I1: IntoIterator<Item = (K, V)>,
I2: IntoIterator<Item = (K, V)>,
{
use std::cmp::Ordering;
let mut it1 = input1.into_iter().peekable();
let mut it2 = input2.into_iter().peekable();
let input1_len = it1.size_hint().0;
let input2_len = it2.size_hint().0;
let mut result = Vec::with_capacity(input1_len + input2_len);
loop {
let which = match (it1.peek(), it2.peek()) {
(Some((l, _)), Some((r, _))) => Some(l.cmp(r)),
(Some(_), None) => Some(Ordering::Less),
(None, Some(_)) => Some(Ordering::Greater),
(None, None) => None,
};
match which {
Some(Ordering::Less) => {
result.push(it1.next().unwrap());
}
Some(Ordering::Equal) => {
let (k, v) = it1.next().unwrap();
let (k2, v2) = it2.next().unwrap();
result.push((k, v));
if preserve_duplicates {
result.push((k2, v2));
}
}
Some(Ordering::Greater) => {
result.push(it2.next().unwrap());
}
None => break,
}
}
result
}
fn merge_sorted<K, V, I1, I2>(input1: I1, input2: I2) -> Vec<(K, V)>
where
K: Ord,
I1: IntoIterator<Item = (K, V)>,
I2: IntoIterator<Item = (K, V)>,
{
merge_sorted_help(input1, input2, false)
}
fn merge_sorted_preserving_duplicates<K, V, I1, I2>(input1: I1, input2: I2) -> Vec<(K, V)>
where
K: Ord,
I1: IntoIterator<Item = (K, V)>,
I2: IntoIterator<Item = (K, V)>,
{
merge_sorted_help(input1, input2, true)
}
fn separate<K, V, I1, I2>(input1: I1, input2: I2) -> Separate<K, V>
where
K: Ord,
I1: IntoIterator<Item = (K, V)>,
I2: IntoIterator<Item = (K, V)>,
{
use std::cmp::Ordering;
let mut it1 = input1.into_iter().peekable();
let mut it2 = input2.into_iter().peekable();
let input1_len = it1.size_hint().0;
let input2_len = it2.size_hint().0;
let max_common = std::cmp::min(input1_len, input2_len);
let mut result = Separate {
only_in_1: Vec::with_capacity(input1_len),
only_in_2: Vec::with_capacity(input2_len),
in_both: Vec::with_capacity(max_common),
};
loop {
let which = match (it1.peek(), it2.peek()) {
(Some((l, _)), Some((r, _))) => Some(l.cmp(r)),
(Some(_), None) => Some(Ordering::Less),
(None, Some(_)) => Some(Ordering::Greater),
(None, None) => None,
};
match which {
Some(Ordering::Less) => {
result.only_in_1.push(it1.next().unwrap());
}
Some(Ordering::Equal) => {
let (k, v1) = it1.next().unwrap();
let (_, v2) = it2.next().unwrap();
result.in_both.push((k, (v1, v2)));
}
Some(Ordering::Greater) => {
result.only_in_2.push(it2.next().unwrap());
}
None => break,
}
}
result
}
fn separate_union_tags(
subs: &Subs,
fields1: UnionTags,
ext1: Variable,
fields2: UnionTags,
ext2: Variable,
) -> (Separate<TagName, VariableSubsSlice>, Variable, Variable) {
let (it1, new_ext1) = fields1.sorted_slices_iterator_and_ext(subs, ext1);
let (it2, new_ext2) = fields2.sorted_slices_iterator_and_ext(subs, ext2);
(separate(it1, it2), new_ext1, new_ext2)
}
#[derive(Debug, Copy, Clone)]
enum Rec {
None,
Left(Variable),
Right(Variable),
Both(Variable, Variable),
}
/// Checks if an extension type `ext1` should be permitted to grow by the `candidate_type`, if it
/// uninhabited.
///
/// This is relevant for the unification strategy of branch and condition
/// types, where we would like
///
/// x : Result Str []
/// when x is
/// Ok x -> x
///
/// to typecheck, because [Ok Str][] = [Ok Str, Result []][] (up to isomorphism).
///
/// Traditionally, such types do not unify because the right-hand side is seen to have a
/// `Result` constructor, but for branches, that constructor is not material.
///
/// Note that such a unification is sound, precisely because `Result` is uninhabited!
///
/// NB: the tag union extension to grow should be `ext1`, and the candidate
/// NB: uninhabited type should be `candidate_type`.
/// of the variables under unification
fn should_extend_ext_with_uninhabited_type(
subs: &Subs,
ext1: Variable,
candidate_type: Variable,
) -> bool {
matches!(
subs.get_content_without_compacting(ext1),
Content::Structure(FlatType::EmptyTagUnion)
) && !subs.is_inhabited(candidate_type)
}
/// After extending an empty tag union extension type [with uninhabited
/// variants][should_extend_ext_with_uninhabited_type], the extension type must be closed again.
fn close_uninhabited_extended_union(subs: &mut Subs, mut var: Variable) {
loop {
match subs.get_content_without_compacting(var) {
Structure(FlatType::EmptyTagUnion) => {
return;
}
FlexVar(..) | FlexAbleVar(..) => {
subs.set_content_unchecked(var, Structure(FlatType::EmptyTagUnion));
return;
}
Structure(FlatType::TagUnion(_, ext))
| Structure(FlatType::RecursiveTagUnion(_, _, ext)) => {
var = *ext;
}
_ => internal_error!("not a tag union"),
}
}
}
#[allow(clippy::too_many_arguments)]
#[must_use]
fn unify_tag_unions<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
tags1: UnionTags,
initial_ext1: Variable,
tags2: UnionTags,
initial_ext2: Variable,
recursion_var: Rec,
) -> Outcome<M> {
let (separate, mut ext1, mut ext2) =
separate_union_tags(env.subs, tags1, initial_ext1, tags2, initial_ext2);
let shared_tags = separate.in_both;
if let (true, Content::Structure(FlatType::EmptyTagUnion)) =
(ctx.mode.is_present(), env.subs.get(ext1).content)
{
if !separate.only_in_2.is_empty() {
// Create a new extension variable that we'll fill in with the
// contents of the tag union from our presence contraint.
//
// If there's no new (toplevel) tags we need to register for
// presence, for example in the cases
// [A] += [A]
// [A, B] += [A]
// [A M, B] += [A N]
// then we don't need to create a fresh ext variable, since the
// tag union is definitely not growing on the top level.
// Notice that in the last case
// [A M, B] += [A N]
// the nested tag `A` **will** grow, but we don't need to modify
// the top level extension variable for that!
let new_ext = fresh(env, pool, ctx, Content::FlexVar(None));
let new_union = Structure(FlatType::TagUnion(tags1, new_ext));
let mut new_desc = ctx.first_desc;
new_desc.content = new_union;
env.subs.set(ctx.first, new_desc);
ext1 = new_ext;
}
}
if separate.only_in_1.is_empty() {
if separate.only_in_2.is_empty() {
let ext_outcome = if ctx.mode.is_eq() {
unify_pool(env, pool, ext1, ext2, ctx.mode)
} else {
// In a presence context, we don't care about ext2 being equal to ext1
Outcome::default()
};
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
let mut shared_tags_outcome = unify_shared_tags_new(
env,
pool,
ctx,
shared_tags,
OtherTags2::Empty,
ext1,
recursion_var,
);
shared_tags_outcome.union(ext_outcome);
shared_tags_outcome
} else {
let extra_tags_in_2 = {
let unique_tags2 = UnionTags::insert_slices_into_subs(env.subs, separate.only_in_2);
let flat_type = FlatType::TagUnion(unique_tags2, ext2);
fresh(env, pool, ctx, Structure(flat_type))
};
// SPECIAL-CASE: if we can grow empty extensions with uninhabited types,
// patch `ext1` to grow accordingly.
let extend_ext_with_uninhabited =
should_extend_ext_with_uninhabited_type(env.subs, ext1, extra_tags_in_2);
if extend_ext_with_uninhabited {
let new_ext = fresh(env, pool, ctx, Content::FlexVar(None));
let new_union = Structure(FlatType::TagUnion(tags1, new_ext));
let mut new_desc = ctx.first_desc;
new_desc.content = new_union;
env.subs.set(ctx.first, new_desc);
ext1 = new_ext;
}
let ext_outcome = unify_pool(env, pool, ext1, extra_tags_in_2, ctx.mode);
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
let mut shared_tags_outcome = unify_shared_tags_new(
env,
pool,
ctx,
shared_tags,
OtherTags2::Empty,
extra_tags_in_2,
recursion_var,
);
shared_tags_outcome.union(ext_outcome);
if extend_ext_with_uninhabited {
close_uninhabited_extended_union(env.subs, ctx.first);
}
shared_tags_outcome
}
} else if separate.only_in_2.is_empty() {
let extra_tags_in_1 = {
let unique_tags1 = UnionTags::insert_slices_into_subs(env.subs, separate.only_in_1);
let flat_type = FlatType::TagUnion(unique_tags1, ext1);
fresh(env, pool, ctx, Structure(flat_type))
};
let mut total_outcome = Outcome::default();
// In a presence context, we don't care about ext2 being equal to tags1
let extend_ext_with_uninhabited = if ctx.mode.is_eq() {
// SPECIAL-CASE: if we can grow empty extensions with uninhabited types,
// patch `ext2` to grow accordingly.
let extend_ext_with_uninhabited =
should_extend_ext_with_uninhabited_type(env.subs, ext2, extra_tags_in_1);
if extend_ext_with_uninhabited {
let new_ext = fresh(env, pool, ctx, Content::FlexVar(None));
let new_union = Structure(FlatType::TagUnion(tags2, new_ext));
let mut new_desc = ctx.second_desc;
new_desc.content = new_union;
env.subs.set(ctx.second, new_desc);
ext2 = new_ext;
}
let ext_outcome = unify_pool(env, pool, extra_tags_in_1, ext2, ctx.mode);
if !ext_outcome.mismatches.is_empty() {
return ext_outcome;
}
total_outcome.union(ext_outcome);
extend_ext_with_uninhabited
} else {
false
};
let shared_tags_outcome = unify_shared_tags_new(
env,
pool,
ctx,
shared_tags,
OtherTags2::Empty,
extra_tags_in_1,
recursion_var,
);
total_outcome.union(shared_tags_outcome);
if extend_ext_with_uninhabited {
close_uninhabited_extended_union(env.subs, ctx.first);
}
total_outcome
} else {
let other_tags = OtherTags2::Union(separate.only_in_1.clone(), separate.only_in_2.clone());
let unique_tags1 = UnionTags::insert_slices_into_subs(env.subs, separate.only_in_1);
let unique_tags2 = UnionTags::insert_slices_into_subs(env.subs, separate.only_in_2);
let ext_content = if ctx.mode.is_present() {
Content::Structure(FlatType::EmptyTagUnion)
} else {
Content::FlexVar(None)
};
let ext = fresh(env, pool, ctx, ext_content);
let flat_type1 = FlatType::TagUnion(unique_tags1, ext);
let flat_type2 = FlatType::TagUnion(unique_tags2, ext);
let sub1 = fresh(env, pool, ctx, Structure(flat_type1));
let sub2 = fresh(env, pool, ctx, Structure(flat_type2));
// NOTE: for clearer error messages, we rollback unification of the ext vars when either fails
//
// This is inspired by
//
//
// f : [Red, Green] -> Bool
// f = \_ -> True
//
// f Blue
//
// In this case, we want the mismatch to be between `[Blue]a` and `[Red, Green]`, but
// without rolling back, the mismatch is between `[Blue, Red, Green]a` and `[Red, Green]`.
// TODO is this also required for the other cases?
let mut total_outcome = Outcome::default();
let snapshot = env.subs.snapshot();
let ext1_outcome = unify_pool(env, pool, ext1, sub2, ctx.mode);
if !ext1_outcome.mismatches.is_empty() {
env.subs.rollback_to(snapshot);
return ext1_outcome;
}
total_outcome.union(ext1_outcome);
if ctx.mode.is_eq() {
let ext2_outcome = unify_pool(env, pool, sub1, ext2, ctx.mode);
if !ext2_outcome.mismatches.is_empty() {
env.subs.rollback_to(snapshot);
return ext2_outcome;
}
total_outcome.union(ext2_outcome);
}
env.subs.commit_snapshot(snapshot);
let shared_tags_outcome =
unify_shared_tags_new(env, pool, ctx, shared_tags, other_tags, ext, recursion_var);
total_outcome.union(shared_tags_outcome);
total_outcome
}
}
#[derive(Debug)]
enum OtherTags2 {
Empty,
Union(
Vec<(TagName, VariableSubsSlice)>,
Vec<(TagName, VariableSubsSlice)>,
),
}
/// Promotes a non-recursive tag union or lambda set to its recursive variant, if it is found to be
/// recursive.
fn maybe_mark_union_recursive(env: &mut Env, union_var: Variable) {
let subs = &mut env.subs;
'outer: while let Err((_, chain)) = subs.occurs(union_var) {
// walk the chain till we find a tag union or lambda set, starting from the variable that
// occurred recursively, which is always at the end of the chain.
for &v in chain.iter().rev() {
let description = subs.get(v);
match description.content {
Content::Structure(FlatType::TagUnion(tags, ext_var)) => {
subs.mark_tag_union_recursive(v, tags, ext_var);
continue 'outer;
}
LambdaSet(self::LambdaSet {
solved,
recursion_var: OptVariable::NONE,
unspecialized,
ambient_function: ambient_function_var,
}) => {
subs.mark_lambda_set_recursive(v, solved, unspecialized, ambient_function_var);
continue 'outer;
}
_ => { /* fall through */ }
}
}
// Might not be any tag union if we only pass through `Apply`s. Otherwise, we have a bug!
if chain.iter().all(|&v| {
matches!(
subs.get_content_without_compacting(v),
Content::Structure(FlatType::Apply(..))
)
}) {
return;
} else {
internal_error!("recursive loop does not contain a tag union")
}
}
}
fn choose_merged_var(subs: &Subs, var1: Variable, var2: Variable) -> Variable {
// If one of the variables is a recursion var, keep that one, so that we avoid inlining
// a recursive tag union type content where we should have a recursion var instead.
//
// When might this happen? For example, in the code
//
// Indirect : [Indirect ConsList]
//
// ConsList : [Nil, Cons Indirect]
//
// l : ConsList
// l = Cons (Indirect (Cons (Indirect Nil)))
// # ^^^^^^^^^^^^^^^~~~~~~~~~~~~~~~~~~~~~^ region-a
// # ~~~~~~~~~~~~~~~~~~~~~ region-b
// l
//
// Suppose `ConsList` has the expanded type `[Nil, Cons [Indirect <rec>]] as <rec>`.
// After unifying the tag application annotated "region-b" with the recursion variable `<rec>`,
// we might have that e.g. `actual` is `<rec>` and `expected` is `[Cons (Indirect ...)]`.
//
// Now, we need to be careful to set the type we choose to represent the merged type
// here to be `<rec>`, not the tag union content of `expected`! Otherwise, we will
// have lost a recursion variable in the recursive tag union.
//
// This would not be incorrect from a type perspective, but causes problems later on for e.g.
// layout generation, which expects recursion variables to be placed correctly. Attempting to detect
// this during layout generation does not work so well because it may be that there *are* recursive
// tag unions that should be inlined, and not pass through recursion variables. So instead, resolve
// these cases here.
//
// See tests labeled "issue_2810" for more examples.
match (
(var1, subs.get_content_unchecked(var1)),
(var2, subs.get_content_unchecked(var2)),
) {
((var, Content::RecursionVar { .. }), _) | (_, (var, Content::RecursionVar { .. })) => var,
_ => var1,
}
}
#[must_use]
fn unify_shared_tags_new<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
shared_tags: Vec<(TagName, (VariableSubsSlice, VariableSubsSlice))>,
other_tags: OtherTags2,
ext: Variable,
recursion_var: Rec,
) -> Outcome<M> {
let mut matching_tags = Vec::default();
let num_shared_tags = shared_tags.len();
let mut total_outcome = Outcome::default();
for (name, (actual_vars, expected_vars)) in shared_tags {
let mut matching_vars = Vec::with_capacity(actual_vars.len());
let actual_len = actual_vars.len();
let expected_len = expected_vars.len();
for (actual_index, expected_index) in actual_vars.into_iter().zip(expected_vars.into_iter())
{
let actual = env.subs[actual_index];
let expected = env.subs[expected_index];
// NOTE the arguments of a tag can be recursive. For instance in the expression
//
// ConsList a : [Nil, Cons a (ConsList a)]
//
// Cons 1 (Cons "foo" Nil)
//
// We need to not just check the outer layer (inferring ConsList Int)
// but also the inner layer (finding a type error, as desired)
//
// This correction introduces the same issue as in https://github.com/elm/compiler/issues/1964
// Polymorphic recursion is now a type error.
//
// The strategy is to expand the recursive tag union as deeply as the non-recursive one
// is.
//
// > RecursiveTagUnion(rvar, [Cons a rvar, Nil], ext)
//
// Conceptually becomes
//
// > RecursiveTagUnion(rvar, [Cons a [Cons a rvar, Nil], Nil], ext)
//
// and so on until the whole non-recursive tag union can be unified with it.
//
// One thing we have to watch out for is that a tag union we're hoping to
// match a recursive tag union with didn't itself become recursive. If it has,
// since we're expanding tag unions to equal depths as described above,
// we'll always pass through this branch. So, we promote tag unions to recursive
// ones here if it turns out they are that.
maybe_mark_union_recursive(env, actual);
maybe_mark_union_recursive(env, expected);
let mut outcome = Outcome::<M>::default();
outcome.union(unify_pool(env, pool, actual, expected, ctx.mode));
if outcome.mismatches.is_empty() {
let merged_var = choose_merged_var(env.subs, actual, expected);
matching_vars.push(merged_var);
}
total_outcome.union(outcome);
}
// only do this check after unification so the error message has more info
if actual_len == expected_len && actual_len == matching_vars.len() {
matching_tags.push((name, matching_vars));
}
}
if num_shared_tags == matching_tags.len() {
// pull fields in from the ext_var
let (ext_fields, new_ext_var) = UnionTags::default().sorted_iterator_and_ext(env.subs, ext);
let ext_fields: Vec<_> = ext_fields
.into_iter()
.map(|(label, variables)| (label, variables.to_vec()))
.collect();
let new_tags: UnionTags = match other_tags {
OtherTags2::Empty => {
if ext_fields.is_empty() {
UnionTags::insert_into_subs(env.subs, matching_tags)
} else {
let all_fields = merge_sorted(matching_tags, ext_fields);
UnionTags::insert_into_subs(env.subs, all_fields)
}
}
OtherTags2::Union(other1, other2) => {
let mut all_fields = merge_sorted(matching_tags, ext_fields);
all_fields = merge_sorted(
all_fields,
other1.into_iter().map(|(field_name, subs_slice)| {
let vec = env.subs.get_subs_slice(subs_slice).to_vec();
(field_name, vec)
}),
);
all_fields = merge_sorted(
all_fields,
other2.into_iter().map(|(field_name, subs_slice)| {
let vec = env.subs.get_subs_slice(subs_slice).to_vec();
(field_name, vec)
}),
);
UnionTags::insert_into_subs(env.subs, all_fields)
}
};
let merge_outcome =
unify_shared_tags_merge_new(env, ctx, new_tags, new_ext_var, recursion_var);
total_outcome.union(merge_outcome);
total_outcome
} else {
mismatch!(
"Problem with Tag Union\nThere should be {:?} matching tags, but I only got \n{:?}",
num_shared_tags,
&matching_tags
)
}
}
#[must_use]
fn unify_shared_tags_merge_new<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
new_tags: UnionTags,
new_ext_var: Variable,
recursion_var: Rec,
) -> Outcome<M> {
let flat_type = match recursion_var {
Rec::None => FlatType::TagUnion(new_tags, new_ext_var),
Rec::Left(rec) | Rec::Right(rec) | Rec::Both(rec, _) => {
debug_assert!(is_recursion_var(env.subs, rec));
FlatType::RecursiveTagUnion(rec, new_tags, new_ext_var)
}
};
merge(env, ctx, Structure(flat_type))
}
#[inline(always)]
#[must_use]
fn unify_flat_type<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
left: &FlatType,
right: &FlatType,
) -> Outcome<M> {
use roc_types::subs::FlatType::*;
match (left, right) {
(EmptyRecord, EmptyRecord) => merge(env, ctx, Structure(*left)),
(Record(fields, ext), EmptyRecord) if fields.has_only_optional_fields(env.subs) => {
unify_pool(env, pool, *ext, ctx.second, ctx.mode)
}
(EmptyRecord, Record(fields, ext)) if fields.has_only_optional_fields(env.subs) => {
unify_pool(env, pool, ctx.first, *ext, ctx.mode)
}
(Record(fields1, ext1), Record(fields2, ext2)) => {
unify_record(env, pool, ctx, *fields1, *ext1, *fields2, *ext2)
}
(EmptyTagUnion, EmptyTagUnion) => merge(env, ctx, Structure(*left)),
(TagUnion(tags, ext), EmptyTagUnion) if tags.is_empty() => {
unify_pool(env, pool, *ext, ctx.second, ctx.mode)
}
(EmptyTagUnion, TagUnion(tags, ext)) if tags.is_empty() => {
unify_pool(env, pool, ctx.first, *ext, ctx.mode)
}
(TagUnion(tags1, ext1), TagUnion(tags2, ext2)) => {
unify_tag_unions(env, pool, ctx, *tags1, *ext1, *tags2, *ext2, Rec::None)
}
(RecursiveTagUnion(recursion_var, tags1, ext1), TagUnion(tags2, ext2)) => {
debug_assert!(is_recursion_var(env.subs, *recursion_var));
// this never happens in type-correct programs, but may happen if there is a type error
let rec = Rec::Left(*recursion_var);
unify_tag_unions(env, pool, ctx, *tags1, *ext1, *tags2, *ext2, rec)
}
(TagUnion(tags1, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => {
debug_assert!(is_recursion_var(env.subs, *recursion_var));
let rec = Rec::Right(*recursion_var);
unify_tag_unions(env, pool, ctx, *tags1, *ext1, *tags2, *ext2, rec)
}
(RecursiveTagUnion(rec1, tags1, ext1), RecursiveTagUnion(rec2, tags2, ext2)) => {
debug_assert!(is_recursion_var(env.subs, *rec1));
debug_assert!(is_recursion_var(env.subs, *rec2));
let rec = Rec::Both(*rec1, *rec2);
let mut outcome = unify_tag_unions(env, pool, ctx, *tags1, *ext1, *tags2, *ext2, rec);
outcome.union(unify_pool(env, pool, *rec1, *rec2, ctx.mode));
outcome
}
(Apply(l_symbol, l_args), Apply(r_symbol, r_args)) if l_symbol == r_symbol => {
let mut outcome = unify_zip_slices(env, pool, *l_args, *r_args, ctx.mode);
if outcome.mismatches.is_empty() {
outcome.union(merge(env, ctx, Structure(Apply(*r_symbol, *r_args))));
}
outcome
}
(Func(l_args, l_closure, l_ret), Func(r_args, r_closure, r_ret))
if l_args.len() == r_args.len() =>
{
let arg_outcome = unify_zip_slices(env, pool, *l_args, *r_args, ctx.mode);
let ret_outcome = unify_pool(env, pool, *l_ret, *r_ret, ctx.mode);
let closure_outcome = unify_pool(env, pool, *l_closure, *r_closure, ctx.mode);
let mut outcome = ret_outcome;
outcome.union(closure_outcome);
outcome.union(arg_outcome);
if outcome.mismatches.is_empty() {
let merged_closure_var = choose_merged_var(env.subs, *l_closure, *r_closure);
outcome.union(merge(
env,
ctx,
Structure(Func(*r_args, merged_closure_var, *r_ret)),
));
}
outcome
}
(FunctionOrTagUnion(tag_names, tag_symbols, ext), Func(args, closure, ret)) => {
unify_function_or_tag_union_and_func(
env,
pool,
ctx,
*tag_names,
*tag_symbols,
*ext,
*args,
*ret,
*closure,
true,
)
}
(Func(args, closure, ret), FunctionOrTagUnion(tag_names, tag_symbols, ext)) => {
unify_function_or_tag_union_and_func(
env,
pool,
ctx,
*tag_names,
*tag_symbols,
*ext,
*args,
*ret,
*closure,
false,
)
}
(
FunctionOrTagUnion(tag_names_1, tag_symbols_1, ext1),
FunctionOrTagUnion(tag_names_2, tag_symbols_2, ext2),
) => unify_two_function_or_tag_unions(
env,
pool,
ctx,
*tag_names_1,
*tag_symbols_1,
*ext1,
*tag_names_2,
*tag_symbols_2,
*ext2,
),
(TagUnion(tags1, ext1), FunctionOrTagUnion(tag_names, _, ext2)) => {
let empty_tag_var_slices = SubsSlice::extend_new(
&mut env.subs.variable_slices,
std::iter::repeat(Default::default()).take(tag_names.len()),
);
let tags2 = UnionTags::from_slices(*tag_names, empty_tag_var_slices);
unify_tag_unions(env, pool, ctx, *tags1, *ext1, tags2, *ext2, Rec::None)
}
(FunctionOrTagUnion(tag_names, _, ext1), TagUnion(tags2, ext2)) => {
let empty_tag_var_slices = SubsSlice::extend_new(
&mut env.subs.variable_slices,
std::iter::repeat(Default::default()).take(tag_names.len()),
);
let tags1 = UnionTags::from_slices(*tag_names, empty_tag_var_slices);
unify_tag_unions(env, pool, ctx, tags1, *ext1, *tags2, *ext2, Rec::None)
}
(RecursiveTagUnion(recursion_var, tags1, ext1), FunctionOrTagUnion(tag_names, _, ext2)) => {
// this never happens in type-correct programs, but may happen if there is a type error
debug_assert!(is_recursion_var(env.subs, *recursion_var));
let empty_tag_var_slices = SubsSlice::extend_new(
&mut env.subs.variable_slices,
std::iter::repeat(Default::default()).take(tag_names.len()),
);
let tags2 = UnionTags::from_slices(*tag_names, empty_tag_var_slices);
let rec = Rec::Left(*recursion_var);
unify_tag_unions(env, pool, ctx, *tags1, *ext1, tags2, *ext2, rec)
}
(FunctionOrTagUnion(tag_names, _, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => {
debug_assert!(is_recursion_var(env.subs, *recursion_var));
let empty_tag_var_slices = SubsSlice::extend_new(
&mut env.subs.variable_slices,
std::iter::repeat(Default::default()).take(tag_names.len()),
);
let tags1 = UnionTags::from_slices(*tag_names, empty_tag_var_slices);
let rec = Rec::Right(*recursion_var);
unify_tag_unions(env, pool, ctx, tags1, *ext1, *tags2, *ext2, rec)
}
// these have underscores because they're unused in --release builds
(_other1, _other2) => {
// any other combination is a mismatch
mismatch!(
"Trying to unify two flat types that are incompatible: {:?} ~ {:?}",
roc_types::subs::SubsFmtFlatType(_other1, env.subs),
roc_types::subs::SubsFmtFlatType(_other2, env.subs)
)
}
}
}
#[must_use]
fn unify_zip_slices<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
left: SubsSlice<Variable>,
right: SubsSlice<Variable>,
mode: Mode,
) -> Outcome<M> {
let mut outcome = Outcome::default();
let it = left.into_iter().zip(right.into_iter());
for (l_index, r_index) in it {
let l_var = env.subs[l_index];
let r_var = env.subs[r_index];
outcome.union(unify_pool(env, pool, l_var, r_var, mode));
}
outcome
}
#[inline(always)]
#[must_use]
fn unify_rigid<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
name: &SubsIndex<Lowercase>,
other: &Content,
) -> Outcome<M> {
match other {
FlexVar(_) => {
// If the other is flex, rigid wins!
merge(env, ctx, RigidVar(*name))
}
FlexAbleVar(_, other_ability) => {
// Mismatch - Rigid can unify with FlexAble only when the Rigid has an ability
// bound as well, otherwise the user failed to correctly annotate the bound.
mismatch!(
%not_able, ctx.first, env.subs.get_subs_slice(*other_ability),
"Rigid {:?} with FlexAble {:?}", ctx.first, other
)
}
RangedNumber(..) => {
// Int a vs Int <range>, the rigid wins
merge(env, ctx, RigidVar(*name))
}
RigidVar(_)
| RigidAbleVar(..)
| RecursionVar { .. }
| Structure(_)
| Alias(..)
| LambdaSet(..) => {
// Type mismatch! Rigid can only unify with flex, even if the
// rigid names are the same.
mismatch!("Rigid {:?} with {:?}", ctx.first, &other)
}
Error => {
// Error propagates.
merge(env, ctx, Error)
}
}
}
#[inline(always)]
fn abilities_are_superset(superset: &[Symbol], subset: &[Symbol]) -> bool {
subset.iter().all(|ability| superset.contains(ability))
}
#[inline(always)]
#[must_use]
fn unify_rigid_able<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
name: &SubsIndex<Lowercase>,
abilities_slice: SubsSlice<Symbol>,
other: &Content,
) -> Outcome<M> {
match other {
FlexVar(_) => {
// If the other is flex, rigid wins!
merge(env, ctx, RigidAbleVar(*name, abilities_slice))
}
FlexAbleVar(_, other_abilities_slice) => {
let (abilities, other_abilities) = (
env.subs.get_subs_slice(abilities_slice),
env.subs.get_subs_slice(*other_abilities_slice),
);
if abilities_are_superset(abilities, other_abilities) {
// The rigid has all the ability bounds of the flex, so rigid wins!
merge(env, ctx, RigidAbleVar(*name, abilities_slice))
} else {
// Mismatch for now.
// TODO check ability hierarchies.
mismatch!(
%not_able, ctx.second, abilities,
"RigidAble {:?} with abilities {:?} not compatible with abilities {:?}",
ctx.first,
abilities,
other_abilities,
)
}
}
RigidVar(_)
| RigidAbleVar(..)
| RecursionVar { .. }
| Structure(_)
| Alias(..)
| RangedNumber(..)
| LambdaSet(..) => {
// Type mismatch! Rigid can only unify with flex, even if the
// rigid names are the same.
mismatch!("Rigid {:?} with {:?}", ctx.first, &other)
}
Error => {
// Error propagates.
merge(env, ctx, Error)
}
}
}
#[inline(always)]
#[must_use]
fn unify_flex<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
opt_name: &Option<SubsIndex<Lowercase>>,
other: &Content,
) -> Outcome<M> {
match other {
FlexVar(other_opt_name) => {
// Prefer using right's name.
let opt_name = opt_name.or(*other_opt_name);
merge(env, ctx, FlexVar(opt_name))
}
FlexAbleVar(opt_other_name, ability) => {
// Prefer using right's name.
let opt_name = (opt_other_name).or(*opt_name);
merge(env, ctx, FlexAbleVar(opt_name, *ability))
}
RigidVar(_)
| RigidAbleVar(_, _)
| RecursionVar { .. }
| Structure(_)
| Alias(_, _, _, _)
| RangedNumber(..)
| LambdaSet(..) => {
// TODO special-case boolean here
// In all other cases, if left is flex, defer to right.
merge(env, ctx, *other)
}
Error => merge(env, ctx, Error),
}
}
// TODO remove once https://github.com/rust-lang/rust/issues/53485 is stabilized
#[cfg(debug_assertions)]
fn is_sorted_dedup<T: Ord>(l: &[T]) -> bool {
let mut iter = l.iter().peekable();
while let Some(before) = iter.next() {
if let Some(after) = iter.peek() {
if before >= after {
return false;
}
}
}
true
}
#[inline(always)]
pub fn merged_ability_slices(
subs: &mut Subs,
left_slice: SubsSlice<Symbol>,
right_slice: SubsSlice<Symbol>,
) -> SubsSlice<Symbol> {
// INVARIANT: abilities slices are inserted sorted into subs
let left = subs.get_subs_slice(left_slice);
let right = subs.get_subs_slice(right_slice);
#[cfg(debug_assertions)]
{
debug_assert!(is_sorted_dedup(left));
debug_assert!(is_sorted_dedup(right));
}
// In practice, ability lists should be very short, so check prefix runs foremost.
if left.starts_with(right) {
return left_slice;
}
if right.starts_with(left) {
return right_slice;
}
let merged = merge_sorted_keys(left.iter().copied(), right.iter().copied());
// TODO: check if there's an existing run in subs rather than re-inserting
SubsSlice::extend_new(&mut subs.symbol_names, merged)
}
#[inline(always)]
#[must_use]
fn unify_flex_able<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
opt_name: &Option<SubsIndex<Lowercase>>,
abilities_slice: SubsSlice<Symbol>,
other: &Content,
) -> Outcome<M> {
match other {
FlexVar(opt_other_name) => {
// Prefer using right's name.
let opt_name = (opt_other_name).or(*opt_name);
merge(env, ctx, FlexAbleVar(opt_name, abilities_slice))
}
FlexAbleVar(opt_other_name, other_abilities_slice) => {
// Prefer the right's name when possible.
let opt_name = (opt_other_name).or(*opt_name);
let merged_abilities =
merged_ability_slices(env.subs, abilities_slice, *other_abilities_slice);
merge(env, ctx, FlexAbleVar(opt_name, merged_abilities))
}
RigidAbleVar(_, other_abilities_slice) => {
let (abilities, other_abilities) = (
env.subs.get_subs_slice(abilities_slice),
env.subs.get_subs_slice(*other_abilities_slice),
);
if abilities_are_superset(other_abilities, abilities) {
// Rigid has all the ability bounds of the flex, so rigid wins!
merge(env, ctx, *other)
} else {
mismatch!(%not_able, ctx.second, abilities, "RigidAble {:?} vs {:?}", abilities, other_abilities)
}
}
RigidVar(_) => mismatch!("FlexAble can never unify with non-able Rigid"),
LambdaSet(..) => mismatch!("FlexAble with LambdaSet"),
Alias(name, _args, _real_var, AliasKind::Opaque) => {
// Opaque type wins
merge_flex_able_with_concrete(
env,
ctx,
ctx.first,
abilities_slice,
*other,
opaque_obligation(*name, ctx.second),
)
}
RecursionVar { .. }
| Structure(_)
| Alias(_, _, _, AliasKind::Structural)
| RangedNumber(..) => {
// Structural type wins.
merge_flex_able_with_concrete(
env,
ctx,
ctx.first,
abilities_slice,
*other,
Obligated::Adhoc(ctx.second),
)
}
Error => merge(env, ctx, Error),
}
}
#[must_use]
fn merge_flex_able_with_concrete<M: MetaCollector>(
env: &mut Env,
ctx: &Context,
flex_able_var: Variable,
abilities: SubsSlice<Symbol>,
concrete_content: Content,
concrete_obligation: Obligated,
) -> Outcome<M> {
let mut outcome = merge(env, ctx, concrete_content);
for &ability in env.subs.get_subs_slice(abilities) {
let must_implement_ability = MustImplementAbility {
typ: concrete_obligation,
ability,
};
outcome.must_implement_ability.push(must_implement_ability);
}
// Figure which, if any, lambda sets should be specialized thanks to the flex able var
// being instantiated. Now as much as I would love to do that here, we don't, because we might
// be in the middle of solving a module and not resolved all available ability implementations
// yet! Instead we chuck it up in the [Outcome] and let our caller do the resolution.
//
// If we ever organize ability implementations so that they are well-known before any other
// unification is done, they can be solved in-band here!
let uls_of_concrete = env
.subs
.remove_dependent_unspecialized_lambda_sets(flex_able_var);
outcome
.lambda_sets_to_specialize
.extend(flex_able_var, uls_of_concrete);
outcome
}
#[inline(always)]
#[must_use]
fn unify_recursion<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
opt_name: &Option<SubsIndex<Lowercase>>,
structure: Variable,
other: &Content,
) -> Outcome<M> {
match other {
RecursionVar {
opt_name: other_opt_name,
structure: _other_structure,
} => {
// NOTE: structure and other_structure may not be unified yet, but will be
// we should not do that here, it would create an infinite loop!
let name = (*opt_name).or(*other_opt_name);
merge(
env,
ctx,
RecursionVar {
opt_name: name,
structure,
},
)
}
Structure(_) => {
// unify the structure variable with this Structure
unify_pool(env, pool, structure, ctx.second, ctx.mode)
}
RigidVar(_) => {
mismatch!("RecursionVar {:?} with rigid {:?}", ctx.first, &other)
}
RigidAbleVar(..) => {
mismatch!("RecursionVar {:?} with able var {:?}", ctx.first, &other)
}
FlexAbleVar(_, ability) => merge_flex_able_with_concrete(
env,
ctx,
ctx.second,
*ability,
RecursionVar {
structure,
opt_name: *opt_name,
},
Obligated::Adhoc(ctx.first),
),
FlexVar(_) => merge(
env,
ctx,
RecursionVar {
structure,
opt_name: *opt_name,
},
),
Alias(_, _, actual, AliasKind::Structural) => {
// look at the type the alias stands for
unify_pool(env, pool, ctx.first, *actual, ctx.mode)
}
Alias(_, _, _, AliasKind::Opaque) => {
// look at the type the recursion var stands for
unify_pool(env, pool, structure, ctx.second, ctx.mode)
}
RangedNumber(..) => mismatch!(
"RecursionVar {:?} with ranged number {:?}",
ctx.first,
&other
),
LambdaSet(..) => {
debug_assert!(!M::UNIFYING_SPECIALIZATION);
// suppose that the recursion var is a lambda set
unify_pool(env, pool, structure, ctx.second, ctx.mode)
}
Error => merge(env, ctx, Error),
}
}
#[must_use]
pub fn merge<M: MetaCollector>(env: &mut Env, ctx: &Context, content: Content) -> Outcome<M> {
let mut outcome: Outcome<M> = Outcome::default();
if !env.compute_outcome_only {
let rank = ctx.first_desc.rank.min(ctx.second_desc.rank);
let desc = Descriptor {
content,
rank,
mark: Mark::NONE,
copy: OptVariable::NONE,
};
outcome
.extra_metadata
.record_changed_variable(env.subs, ctx.first);
outcome
.extra_metadata
.record_changed_variable(env.subs, ctx.second);
env.subs.union(ctx.first, ctx.second, desc);
}
outcome
}
fn register(env: &mut Env, desc: Descriptor, pool: &mut Pool) -> Variable {
let var = env.subs.fresh(desc);
pool.push(var);
var
}
fn fresh(env: &mut Env, pool: &mut Pool, ctx: &Context, content: Content) -> Variable {
register(
env,
Descriptor {
content,
rank: ctx.first_desc.rank.min(ctx.second_desc.rank),
mark: Mark::NONE,
copy: OptVariable::NONE,
},
pool,
)
}
fn is_recursion_var(subs: &Subs, var: Variable) -> bool {
matches!(
subs.get_content_without_compacting(var),
Content::RecursionVar { .. }
)
}
#[allow(clippy::too_many_arguments)]
#[must_use]
fn unify_function_or_tag_union_and_func<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
tag_names_slice: SubsSlice<TagName>,
tag_fn_lambdas: SubsSlice<Symbol>,
tag_ext: Variable,
function_arguments: VariableSubsSlice,
function_return: Variable,
function_lambda_set: Variable,
left: bool,
) -> Outcome<M> {
let tag_names = env.subs.get_subs_slice(tag_names_slice).to_vec();
let union_tags = UnionTags::insert_slices_into_subs(
env.subs,
tag_names.into_iter().map(|tag| (tag, function_arguments)),
);
let content = Content::Structure(FlatType::TagUnion(union_tags, tag_ext));
let new_tag_union_var = fresh(env, pool, ctx, content);
let mut outcome = if left {
unify_pool(env, pool, new_tag_union_var, function_return, ctx.mode)
} else {
unify_pool(env, pool, function_return, new_tag_union_var, ctx.mode)
};
{
let lambda_names = env.subs.get_subs_slice(tag_fn_lambdas).to_vec();
let new_lambda_names = SubsSlice::extend_new(&mut env.subs.symbol_names, lambda_names);
let empty_captures_slices = SubsSlice::extend_new(
&mut env.subs.variable_slices,
std::iter::repeat(Default::default()).take(new_lambda_names.len()),
);
let union_tags = UnionLambdas::from_slices(new_lambda_names, empty_captures_slices);
let ambient_function_var = if left { ctx.first } else { ctx.second };
let lambda_set_content = LambdaSet(self::LambdaSet {
solved: union_tags,
recursion_var: OptVariable::NONE,
unspecialized: SubsSlice::default(),
ambient_function: ambient_function_var,
});
let tag_lambda_set = register(
env,
Descriptor {
content: lambda_set_content,
rank: ctx.first_desc.rank.min(ctx.second_desc.rank),
mark: Mark::NONE,
copy: OptVariable::NONE,
},
pool,
);
let closure_outcome = if left {
unify_pool(env, pool, tag_lambda_set, function_lambda_set, ctx.mode)
} else {
unify_pool(env, pool, function_lambda_set, tag_lambda_set, ctx.mode)
};
outcome.union(closure_outcome);
}
if outcome.mismatches.is_empty() {
let desc = if left {
env.subs.get(ctx.second)
} else {
env.subs.get(ctx.first)
};
outcome.union(merge(env, ctx, desc.content));
}
outcome
}
#[allow(clippy::too_many_arguments)]
fn unify_two_function_or_tag_unions<M: MetaCollector>(
env: &mut Env,
pool: &mut Pool,
ctx: &Context,
tag_names_1: SubsSlice<TagName>,
tag_symbols_1: SubsSlice<Symbol>,
ext1: Variable,
tag_names_2: SubsSlice<TagName>,
tag_symbols_2: SubsSlice<Symbol>,
ext2: Variable,
) -> Outcome<M> {
let merged_tags = {
let mut all_tags: Vec<_> = (env.subs.get_subs_slice(tag_names_1).iter())
.chain(env.subs.get_subs_slice(tag_names_2))
.cloned()
.collect();
all_tags.sort();
all_tags.dedup();
SubsSlice::extend_new(&mut env.subs.tag_names, all_tags)
};
let merged_lambdas = {
let mut all_lambdas: Vec<_> = (env.subs.get_subs_slice(tag_symbols_1).iter())
.chain(env.subs.get_subs_slice(tag_symbols_2))
.cloned()
.collect();
all_lambdas.sort();
all_lambdas.dedup();
SubsSlice::extend_new(&mut env.subs.symbol_names, all_lambdas)
};
let mut outcome = unify_pool(env, pool, ext1, ext2, ctx.mode);
if !outcome.mismatches.is_empty() {
return outcome;
}
let merge_outcome = merge(
env,
ctx,
Content::Structure(FlatType::FunctionOrTagUnion(
merged_tags,
merged_lambdas,
ext1,
)),
);
outcome.union(merge_outcome);
outcome
}