Merge trunk

This commit is contained in:
Richard Feldman 2020-06-29 19:38:28 -04:00
parent baa3debae2
commit 8c96d12661
26 changed files with 2600 additions and 1093 deletions

View file

@ -1,7 +1,7 @@
use roc_collections::all::{relative_complement, union, MutMap, SendSet};
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol;
use roc_types::boolean_algebra::{Atom, Bool};
use roc_types::boolean_algebra::Bool;
use roc_types::subs::Content::{self, *};
use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable};
use roc_types::types::{gather_fields, ErrorType, Mismatch, RecordStructure};
@ -66,7 +66,7 @@ macro_rules! mismatch {
type Pool = Vec<Variable>;
struct Context {
pub struct Context {
first: Variable,
first_desc: Descriptor,
second: Variable,
@ -128,9 +128,22 @@ pub fn unify_pool(subs: &mut Subs, pool: &mut Pool, var1: Variable, var2: Variab
}
fn unify_context(subs: &mut Subs, pool: &mut Pool, ctx: Context) -> Outcome {
// println!( "{:?} {:?} ~ {:?} {:?}", ctx.first, ctx.first_desc.content, ctx.second, ctx.second_desc.content,);
if false {
// if true, print the types that are unified.
//
// 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");
}
match &ctx.first_desc.content {
FlexVar(opt_name) => unify_flex(subs, pool, &ctx, opt_name, &ctx.second_desc.content),
FlexVar(opt_name) => unify_flex(subs, &ctx, opt_name, &ctx.second_desc.content),
RigidVar(name) => unify_rigid(subs, &ctx, name, &ctx.second_desc.content),
Structure(flat_type) => {
unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content)
@ -193,16 +206,8 @@ fn unify_structure(
) -> Outcome {
match other {
FlexVar(_) => {
// TODO special-case boolean here
match flat_type {
FlatType::Boolean(Bool(Atom::Variable(var), _rest)) => {
unify_pool(subs, pool, *var, ctx.second)
}
_ => {
// If the other is flex, Structure wins!
merge(subs, ctx, Structure(flat_type.clone()))
}
}
// If the other is flex, Structure wins!
merge(subs, ctx, Structure(flat_type.clone()))
}
RigidVar(name) => {
// Type mismatch! Rigid can only unify with flex.
@ -529,7 +534,29 @@ fn unify_shared_tags(
let expected_len = expected_vars.len();
for (actual, expected) in actual_vars.into_iter().zip(expected_vars.into_iter()) {
let problems = unify_pool(subs, pool, actual, expected);
// NOTE the arguments of a tag can be recursive. For instance in the expression
//
// 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.
let problems = if let Some(rvar) = recursion_var {
if expected == rvar {
unify_pool(subs, pool, actual, ctx.second)
} else {
// replace the rvar with ctx.second in expected
subs.explicit_substitute(rvar, ctx.second, expected);
unify_pool(subs, pool, actual, expected)
}
} else {
// we always unify NonRecursive with Recursive, so this should never happen
debug_assert!(Some(actual) != recursion_var);
unify_pool(subs, pool, actual, expected)
};
if problems.is_empty() {
matching_vars.push(actual);
@ -611,6 +638,10 @@ fn unify_flat_type(
unify_tag_union(subs, pool, ctx, union1, union2, (None, None))
}
(RecursiveTagUnion(_, _, _), TagUnion(_, _)) => {
unreachable!("unify of recursive with non-recursive tag union should not occur");
}
(TagUnion(tags1, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => {
let union1 = gather_tags(subs, tags1.clone(), *ext1);
let union2 = gather_tags(subs, tags2.clone(), *ext2);
@ -632,34 +663,69 @@ fn unify_flat_type(
unify_tag_union(subs, pool, ctx, union1, union2, (Some(*rec1), Some(*rec2)))
}
(Boolean(Bool(free1, rest1)), Boolean(Bool(free2, rest2))) => {
// unify the free variables
let (new_free, mut free_var_problems) = unify_free_atoms(subs, pool, *free1, *free2);
(Boolean(b1), Boolean(b2)) => {
use Bool::*;
match (b1, b2) {
(Shared, Shared) => merge(subs, ctx, Structure(left.clone())),
(Shared, Container(cvar, mvars)) => {
let mut outcome = vec![];
// unify everything with shared
outcome.extend(unify_pool(subs, pool, ctx.first, *cvar));
let combined_rest: SendSet<Atom> = rest1
.clone()
.into_iter()
.chain(rest2.clone().into_iter())
.collect::<SendSet<Atom>>();
for mvar in mvars {
outcome.extend(unify_pool(subs, pool, ctx.first, *mvar));
}
let mut combined = if let Err(false) = chase_atom(subs, new_free) {
// if the container is shared, all elements must be shared too
for atom in combined_rest {
let (_, atom_problems) = unify_free_atoms(subs, pool, atom, Atom::Zero);
free_var_problems.extend(atom_problems);
// set the first and second variables to Shared
let content = Content::Structure(FlatType::Boolean(Bool::Shared));
outcome.extend(merge(subs, ctx, content));
outcome
}
Bool(Atom::Zero, SendSet::default())
} else {
Bool(new_free, combined_rest)
};
(Container(cvar, mvars), Shared) => {
let mut outcome = vec![];
// unify everything with shared
outcome.extend(unify_pool(subs, pool, ctx.second, *cvar));
combined.apply_subs(subs);
for mvar in mvars {
outcome.extend(unify_pool(subs, pool, ctx.second, *mvar));
}
// force first and second to equal this new variable
let content = Content::Structure(FlatType::Boolean(combined));
merge(subs, ctx, content);
// set the first and second variables to Shared
let content = Content::Structure(FlatType::Boolean(Bool::Shared));
outcome.extend(merge(subs, ctx, content));
free_var_problems
outcome
}
(Container(cvar1, mvars1), Container(cvar2, mvars2)) => {
let mut outcome = vec![];
// unify cvar1 and cvar2?
outcome.extend(unify_pool(subs, pool, *cvar1, *cvar2));
let mvars: SendSet<Variable> = mvars1
.into_iter()
.chain(mvars2.into_iter())
.copied()
.filter_map(|v| {
let root = subs.get_root_key(v);
if roc_types::boolean_algebra::var_is_shared(subs, root) {
None
} else {
Some(root)
}
})
.collect();
let content =
Content::Structure(FlatType::Boolean(Bool::Container(*cvar1, mvars)));
outcome.extend(merge(subs, ctx, content));
outcome
}
}
}
(Apply(l_symbol, l_args), Apply(r_symbol, r_args)) if l_symbol == r_symbol => {
@ -693,41 +759,6 @@ fn unify_flat_type(
}
}
fn chase_atom(subs: &mut Subs, atom: Atom) -> Result<Variable, bool> {
match atom {
Atom::Zero => Err(false),
Atom::One => Err(true),
Atom::Variable(var) => match subs.get(var).content {
Content::Structure(FlatType::Boolean(Bool(first, rest))) => {
debug_assert!(rest.is_empty());
chase_atom(subs, first)
}
_ => Ok(var),
},
}
}
fn unify_free_atoms(subs: &mut Subs, pool: &mut Pool, b1: Atom, b2: Atom) -> (Atom, Vec<Mismatch>) {
match (b1, b2) {
(Atom::Variable(v1), Atom::Variable(v2)) => {
(Atom::Variable(v1), unify_pool(subs, pool, v1, v2))
}
(Atom::Variable(var), other) | (other, Atom::Variable(var)) => {
subs.set_content(
var,
Content::Structure(FlatType::Boolean(Bool(other, SendSet::default()))),
);
(other, vec![])
}
(Atom::Zero, Atom::Zero) => (Atom::Zero, vec![]),
(Atom::One, Atom::One) => (Atom::One, vec![]),
_ => unreachable!(
"invalid boolean unification. Because we never infer One, this should never happen!"
),
}
}
fn unify_zip<'a, I>(subs: &mut Subs, pool: &mut Pool, left_iter: I, right_iter: I) -> Outcome
where
I: Iterator<Item = &'a Variable>,
@ -765,7 +796,6 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content
#[inline(always)]
fn unify_flex(
subs: &mut Subs,
pool: &mut Pool,
ctx: &Context,
opt_name: &Option<Lowercase>,
other: &Content,
@ -776,10 +806,6 @@ fn unify_flex(
merge(subs, ctx, FlexVar(opt_name.clone()))
}
Structure(FlatType::Boolean(Bool(Atom::Variable(var), _rest))) => {
unify_pool(subs, pool, ctx.first, *var)
}
FlexVar(Some(_)) | RigidVar(_) | Structure(_) | Alias(_, _, _) => {
// TODO special-case boolean here
// In all other cases, if left is flex, defer to right.
@ -791,7 +817,7 @@ fn unify_flex(
}
}
fn merge(subs: &mut Subs, ctx: &Context, content: Content) -> Outcome {
pub fn merge(subs: &mut Subs, ctx: &Context, content: Content) -> Outcome {
let rank = ctx.first_desc.rank.min(ctx.second_desc.rank);
let desc = Descriptor {
content,