rewrite uniqueness logic

This commit is contained in:
Folkert 2020-06-24 23:06:34 +02:00
parent 41ad8f2952
commit 055b01c463
9 changed files with 605 additions and 435 deletions

View file

@ -10,7 +10,7 @@ use roc_collections::all::{ImMap, ImSet, Index, SendMap};
use roc_module::ident::{Ident, Lowercase};
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region};
use roc_types::boolean_algebra::{Atom, Bool};
use roc_types::boolean_algebra::Bool;
use roc_types::subs::{VarStore, Variable};
use roc_types::types::AnnotationSource::{self, *};
use roc_types::types::Type::{self, *};
@ -248,12 +248,11 @@ fn constrain_pattern(
let empty_var = var_store.fresh();
state.vars.push(empty_var);
state.vars.extend(pattern_uniq_vars.clone());
Bool::with_free(
empty_var,
pattern_uniq_vars.into_iter().map(Atom::Variable).collect(),
)
Bool::container(empty_var, pattern_uniq_vars)
};
// dbg!(&record_uniq_type);
let record_type = attr_type(
record_uniq_type,
Type::Record(field_types, Box::new(ext_type)),
@ -305,10 +304,7 @@ fn constrain_pattern(
let empty_var = var_store.fresh();
state.vars.push(empty_var);
state.vars.extend(pattern_uniq_vars.clone());
Bool::with_free(
empty_var,
pattern_uniq_vars.into_iter().map(Atom::Variable).collect(),
)
Bool::container(empty_var, pattern_uniq_vars)
};
let union_type = attr_type(
tag_union_uniq_type,
@ -1252,8 +1248,7 @@ pub fn constrain_expr(
field_types.insert(field.clone(), field_type.clone());
let record_uniq_var = var_store.fresh();
let record_uniq_type =
Bool::with_free(record_uniq_var, vec![Atom::Variable(field_uniq_var)]);
let record_uniq_type = Bool::container(record_uniq_var, vec![field_uniq_var]);
let record_type = attr_type(
record_uniq_type,
Type::Record(field_types, Box::new(Type::Variable(*ext_var))),
@ -1310,8 +1305,7 @@ pub fn constrain_expr(
field_types.insert(field.clone(), field_type.clone());
let record_uniq_var = var_store.fresh();
let record_uniq_type =
Bool::with_free(record_uniq_var, vec![Atom::Variable(field_uniq_var)]);
let record_uniq_type = Bool::container(record_uniq_var, vec![field_uniq_var]);
let record_type = attr_type(
record_uniq_type,
Type::Record(field_types, Box::new(Type::Variable(*ext_var))),
@ -1393,10 +1387,10 @@ fn constrain_var(
applied_usage_constraint.insert(symbol_for_lookup);
let mut variables = Vec::new();
let (free, rest, inner_type) =
let (record_bool, inner_type) =
constrain_by_usage(&usage.expect("wut"), var_store, &mut variables);
let record_type = attr_type(Bool::from_parts(free, rest), inner_type);
let record_type = attr_type(record_bool, inner_type);
// NOTE breaking the expectation up like this REALLY matters!
let new_expected = Expected::NoExpectation(record_type.clone());
@ -1417,128 +1411,176 @@ fn constrain_by_usage(
usage: &Usage,
var_store: &mut VarStore,
introduced: &mut Vec<Variable>,
) -> (Atom, Vec<Atom>, Type) {
use Mark::*;
) -> (Bool, Type) {
use Usage::*;
match usage {
Simple(Shared) => {
Simple(Mark::Shared) => {
let var = var_store.fresh();
introduced.push(var);
(Atom::Zero, vec![], Type::Variable(var))
(Bool::Shared, Type::Variable(var))
}
Simple(Seen) | Simple(Unique) => {
Simple(Mark::Seen) | Simple(Mark::Unique) => {
let var = var_store.fresh();
let uvar = var_store.fresh();
introduced.push(var);
introduced.push(uvar);
(Atom::Variable(uvar), vec![], Type::Variable(var))
(Bool::container(uvar, vec![]), Type::Variable(var))
}
Usage::Access(Container::Record, mark, fields) => {
let (record_uniq_var, atoms, ext_type) =
constrain_by_usage(&Simple(*mark), var_store, introduced);
debug_assert!(atoms.is_empty());
let (record_bool, ext_type) = constrain_by_usage(&Simple(*mark), var_store, introduced);
constrain_by_usage_record(fields, record_uniq_var, ext_type, introduced, var_store)
constrain_by_usage_record(fields, record_bool, ext_type, introduced, var_store)
}
Usage::Update(Container::Record, _, fields) => {
let record_uvar = var_store.fresh();
let record_uniq_var = Atom::Variable(record_uvar);
introduced.push(record_uvar);
let record_bool = Bool::variable(record_uvar);
let ext_var = var_store.fresh();
let ext_type = Type::Variable(ext_var);
introduced.push(ext_var);
constrain_by_usage_record(fields, record_uniq_var, ext_type, introduced, var_store)
constrain_by_usage_record(fields, record_bool, ext_type, introduced, var_store)
}
Usage::Access(Container::List, mark, fields) => {
let (list_uniq_var, atoms, _ext_type) =
constrain_by_usage(&Simple(*mark), var_store, introduced);
debug_assert!(atoms.is_empty());
let (list_bool, _ext_type) = constrain_by_usage(&Simple(*mark), var_store, introduced);
constrain_by_usage_list(fields, list_uniq_var, introduced, var_store)
let field_usage = fields
.get(&sharing::LIST_ELEM.into())
.expect("no LIST_ELEM key");
let (elem_bool, elem_type) = constrain_by_usage(field_usage, var_store, introduced);
match list_bool {
Bool::Shared => (
Bool::Shared,
Type::Apply(Symbol::LIST_LIST, vec![attr_type(Bool::Shared, elem_type)]),
),
Bool::Container(list_uvar, list_mvars) => {
debug_assert!(list_mvars.is_empty());
match elem_bool {
Bool::Shared => (
Bool::variable(list_uvar),
Type::Apply(
Symbol::LIST_LIST,
vec![attr_type(Bool::Shared, elem_type)],
),
),
Bool::Container(cvar, mvars) => {
debug_assert!(mvars.is_empty());
(
Bool::container(list_uvar, vec![cvar]),
Type::Apply(
Symbol::LIST_LIST,
vec![attr_type(Bool::container(cvar, mvars), elem_type)],
),
)
}
}
}
}
}
Usage::Update(Container::List, _, fields) => {
let list_uvar = var_store.fresh();
introduced.push(list_uvar);
let list_uniq_var = Atom::Variable(list_uvar);
constrain_by_usage_list(fields, list_uniq_var, introduced, var_store)
let field_usage = fields
.get(&sharing::LIST_ELEM.into())
.expect("no LIST_ELEM key");
let (elem_bool, elem_type) = constrain_by_usage(field_usage, var_store, introduced);
match elem_bool {
Bool::Shared => (
Bool::variable(list_uvar),
Type::Apply(Symbol::LIST_LIST, vec![attr_type(Bool::Shared, elem_type)]),
),
Bool::Container(cvar, mvars) => {
debug_assert!(mvars.is_empty());
(
Bool::container(list_uvar, vec![cvar]),
Type::Apply(
Symbol::LIST_LIST,
vec![attr_type(Bool::container(cvar, mvars), elem_type)],
),
)
}
}
}
}
}
fn constrain_by_usage_list(
fields: &FieldAccess,
list_uniq_var: Atom,
introduced: &mut Vec<Variable>,
var_store: &mut VarStore,
) -> (Atom, Vec<Atom>, Type) {
let field_usage = fields
.get(&sharing::LIST_ELEM.into())
.expect("no LIST_ELEM key");
let (elem_uvar, atoms, elem_type) = constrain_by_usage(field_usage, var_store, introduced);
debug_assert!(atoms.is_empty());
(
list_uniq_var,
vec![elem_uvar],
Type::Apply(
Symbol::LIST_LIST,
vec![attr_type(Bool::from_parts(elem_uvar, vec![]), elem_type)],
),
)
}
fn constrain_by_usage_record(
fields: &FieldAccess,
record_uniq_var: Atom,
record_bool: Bool,
ext_type: Type,
introduced: &mut Vec<Variable>,
var_store: &mut VarStore,
) -> (Atom, Vec<Atom>, Type) {
) -> (Bool, Type) {
let mut field_types = SendMap::default();
if fields.is_empty() {
(
record_uniq_var,
vec![],
Type::Record(field_types, Box::new(ext_type)),
)
} else {
let mut uniq_vars = Vec::with_capacity(fields.len());
match record_bool {
_ if fields.is_empty() => (record_bool, Type::Record(field_types, Box::new(ext_type))),
Bool::Shared => {
for (lowercase, nested_usage) in fields.clone().into_iter() {
let (_, nested_type) = constrain_by_usage(&nested_usage, var_store, introduced);
for (lowercase, nested_usage) in fields.clone().into_iter() {
let (uvar, atoms, nested_type) =
constrain_by_usage(&nested_usage, var_store, introduced);
let field_type = attr_type(Bool::Shared, nested_type);
for atom in &atoms {
uniq_vars.push(*atom);
field_types.insert(lowercase.clone(), field_type);
}
uniq_vars.push(uvar);
let field_type = attr_type(Bool::from_parts(uvar, atoms), nested_type);
field_types.insert(lowercase.clone(), field_type);
(
Bool::Shared,
Type::Record(
field_types,
// TODO can we avoid doing Box::new on every single one of these?
// For example, could we have a single lazy_static global Box they
// could all share?
Box::new(ext_type),
),
)
}
Bool::Container(record_uniq_var, mvars) => {
debug_assert!(mvars.is_empty());
let mut uniq_vars = Vec::with_capacity(fields.len());
for (lowercase, nested_usage) in fields.clone().into_iter() {
let (nested_bool, nested_type) =
constrain_by_usage(&nested_usage, var_store, introduced);
let field_type = match nested_bool {
Bool::Container(uvar, atoms) => {
for atom in &atoms {
uniq_vars.push(*atom);
}
uniq_vars.push(uvar);
attr_type(Bool::Container(uvar, atoms), nested_type)
}
Bool::Shared => attr_type(Bool::Shared, nested_type),
};
field_types.insert(lowercase.clone(), field_type);
}
(
Bool::container(record_uniq_var, uniq_vars),
Type::Record(
field_types,
// TODO can we avoid doing Box::new on every single one of these?
// For example, could we have a single lazy_static global Box they
// could all share?
Box::new(ext_type),
),
)
}
(
record_uniq_var,
uniq_vars,
Type::Record(
field_types,
// TODO can we avoid doing Box::new on every single one of these?
// For example, could we have a single lazy_static global Box they
// could all share?
Box::new(ext_type),
),
)
}
}
@ -1656,6 +1698,8 @@ fn annotation_to_attr_type(
) -> (Vec<Variable>, Type) {
use roc_types::types::Type::*;
dbg!(&ann);
match ann {
Variable(var) => {
if change_var_kind {
@ -1711,9 +1755,7 @@ fn annotation_to_attr_type(
// A rigid behind an attr has already been lifted, don't do it again!
let (result_vars, result_lifted) = match args[1] {
Type::Variable(_) => match uniq_type {
Type::Boolean(Bool(Atom::Variable(urigid), _)) => {
(vec![urigid], args[1].clone())
}
Type::Boolean(Bool::Container(urigid, _)) => (vec![urigid], args[1].clone()),
_ => (vec![], args[1].clone()),
},
_ => annotation_to_attr_type(var_store, &args[1], rigids, change_var_kind),
@ -1750,6 +1792,8 @@ fn annotation_to_attr_type(
vars.push(uniq_var);
// dbg!(&uniq_var);
(
vars,
attr_type(
@ -1782,29 +1826,53 @@ fn annotation_to_attr_type(
)
}
RecursiveTagUnion(rec_var, tags, ext_type) => {
// In the case of
//
// [ Cons a (List a), Nil ] as List a
//
// We need to lift it to
//
// Attr u ([ Cons a (Attr u (List a)), Nil ] as List a)
//
// So the `u` of the whole recursive tag union is the same as the one used in the recursion
let uniq_var = var_store.fresh();
let mut vars = Vec::with_capacity(tags.len());
let mut lifted_tags = Vec::with_capacity(tags.len());
let mut substitutions = ImMap::default();
substitutions.insert(
*rec_var,
attr_type(Bool::variable(uniq_var), Type::Variable(*rec_var)),
);
for (tag, fields) in tags {
let (new_vars, lifted_fields) =
let (new_vars, mut lifted_fields) =
annotation_to_attr_type_many(var_store, fields, rigids, change_var_kind);
vars.extend(new_vars);
for f in lifted_fields.iter_mut() {
f.substitute(&substitutions);
}
lifted_tags.push((tag.clone(), lifted_fields));
}
vars.push(uniq_var);
(
vars,
attr_type(
Bool::variable(uniq_var),
Type::RecursiveTagUnion(*rec_var, lifted_tags, ext_type.clone()),
),
)
let result = attr_type(
Bool::variable(uniq_var),
Type::RecursiveTagUnion(*rec_var, lifted_tags, ext_type.clone()),
);
dbg!(&result);
(vars, result)
}
Alias(symbol, fields, actual) => {
dbg!(actual);
panic!();
let (mut actual_vars, lifted_actual) =
annotation_to_attr_type(var_store, actual, rigids, change_var_kind);
@ -1855,6 +1923,7 @@ fn annotation_to_attr_type_many(
}
fn aliases_to_attr_type(var_store: &mut VarStore, aliases: &mut SendMap<Symbol, Alias>) {
dbg!(&aliases);
for alias in aliases.iter_mut() {
// ensure
//
@ -1867,14 +1936,8 @@ fn aliases_to_attr_type(var_store: &mut VarStore, aliases: &mut SendMap<Symbol,
// That would give a double attr wrapper on the type arguments.
// The `change_var_kind` flag set to false ensures type variables remain of kind *
let (_, new) = annotation_to_attr_type(var_store, &alias.typ, &mut ImMap::default(), false);
// remove the outer Attr, because when this occurs in a signature it'll already be wrapped in one
match new {
Type::Apply(Symbol::ATTR_ATTR, args) => {
alias.typ = args[1].clone();
}
_ => unreachable!(),
}
alias.typ = new;
}
}