Merge branch 'trunk' of github.com:rtfeldman/roc into list-reverse

This commit is contained in:
Chad Stearns 2020-07-02 21:33:24 -04:00
commit d8a8741aed
24 changed files with 1977 additions and 595 deletions

View file

@ -24,9 +24,9 @@ interface Num
## a more specific type based on how they're used.
##
## For example, in `(1 + List.len myList)`, the `1` has the type `Num *` at first,
## but because `List.len` returns a `Ulen`, the `1` ends up changing from
## `Num *` to the more specific `Ulen`, and the expression as a whole
## ends up having the type `Ulen`.
## but because `List.len` returns a `Len`, the `1` ends up changing from
## `Num *` to the more specific `Len`, and the expression as a whole
## ends up having the type `Len`.
##
## Sometimes number literals don't become more specific. For example,
## the #Num.toStr function has the type `Num * -> Str`. This means that
@ -47,7 +47,7 @@ interface Num
##
## * `215u8` is a `215` value of type #U8
## * `76.4f32` is a `76.4` value of type #F32
## * `12345ulen` is a `12345` value of type `#Ulen`
## * `12345ulen` is a `12345` value of type `#Len`
##
## In practice, these are rarely needed. It's most common to write
## number literals without any suffix.
@ -116,7 +116,7 @@ U64 : Int @U64
I128 : Int @I128
U128 : Int @U128
Ilen : Int @Ilen
Ulen : Int @Ulen
Len : Int @Len
## A 64-bit signed integer. All number literals without decimal points are compatible with #Int values.
##
@ -176,12 +176,15 @@ Ulen : Int @Ulen
## | ` (over 340 undecillion) 0` | #U128 | 16 Bytes |
## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | |
##
## There are also two variable-size integer types: #Ulen and #Ilen. Their sizes
## are determined by the [machine word length](https://en.wikipedia.org/wiki/Word_(computer_architecture))
## of the system you're compiling for. (The "len" in their names is short for "length of a machine word.")
## For example, when compiling for a 64-bit target, #Ulen is the same as #U64,
## and #Ilen is the same as #I64. When compiling for a 32-bit target, #Ulen is the same as #U32,
## and #Ilen is the same as #I32. In practice, #Ulen sees much more use than #Ilen.
## Roc also has one variable-size integer type: #Len. The size of #Len is equal
## to the size of a memory address, which varies by system. For example, when
## compiling for a 64-bit system, #Len is the same as #U64. When compiling for a
## 32-bit system, it's the same as #U32.
##
## A common use for #Len is to store the length ("len" for short) of a
## collection like #List, #Set, or #Map. 64-bit systems can represent longer
## lists in memory than 32-bit sytems can, which is why the length of a list
## is represented as a #Len in Roc.
##
## If any operation would result in an #Int that is either too big
## or too small to fit in that range (e.g. calling `Int.maxI32 + 1`),
@ -579,6 +582,21 @@ hash64 : a -> U64
## Limits
## The highest number that can be stored in a #Len without overflowing its
## available memory and crashing.
##
## Note that this number varies by systems. For example, when building for a
## 64-bit system, this will be equal to #Num.maxU64, but when building for a
## 32-bit system, this will be equal to #Num.maxU32.
maxLen : Len
## The number zero.
##
## #Num.minLen is the lowest number that can be stored in a #Len, which is zero
## because #Len is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number. Unsigned numbers cannot be negative.
minLen : Len
## The highest number that can be stored in an #I32 without overflowing its
## available memory and crashing.
##
@ -593,6 +611,32 @@ maxI32 : I32
## #Int.maxI32, which means if you call #Num.abs on #Int.minI32, it will overflow and crash!
minI32 : I32
## The highest number that can be stored in a #U64 without overflowing its
## available memory and crashing.
##
## For reference, that number is `18_446_744_073_709_551_615`, which is over 18 quintillion.
maxU64 : U64
## The number zero.
##
## #Num.minU64 is the lowest number that can be stored in a #U64, which is zero
## because #U64 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number. Unsigned numbers cannot be negative.
minU64 : U64
## The highest number that can be stored in a #U32 without overflowing its
## available memory and crashing.
##
## For reference, that number is `4_294_967_295`, which is over 4 million.
maxU32 : U32
## The number zero.
##
## #Num.minU32 is the lowest number that can be stored in a #U32, which is zero
## because #U32 is [unsigned](https://en.wikipedia.org/wiki/Signed_number_representations),
## and zero is the lowest unsigned number. Unsigned numbers cannot be negative.
minU32 : U32
## The highest supported #Float value you can have, which is approximately 1.8 × 10^308.
##
## If you go higher than this, your running Roc code will crash - so be careful not to!

View file

@ -3,7 +3,7 @@ use roc_collections::all::{default_hasher, MutMap};
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_types::solved_types::{BuiltinAlias, SolvedAtom, SolvedType};
use roc_types::solved_types::{BuiltinAlias, SolvedBool, SolvedType};
use roc_types::subs::VarId;
use std::collections::HashMap;
@ -41,18 +41,16 @@ const FUVAR: VarId = VarId::from_u32(1000);
fn shared(base: SolvedType) -> SolvedType {
SolvedType::Apply(
Symbol::ATTR_ATTR,
vec![SolvedType::Boolean(SolvedAtom::Zero, vec![]), base],
vec![SolvedType::Boolean(SolvedBool::SolvedShared), base],
)
}
fn boolean(b: VarId) -> SolvedType {
SolvedType::Boolean(SolvedAtom::Variable(b), vec![])
SolvedType::Boolean(SolvedBool::SolvedContainer(b, vec![]))
}
fn container(free: VarId, rest: Vec<VarId>) -> SolvedType {
let solved_rest = rest.into_iter().map(SolvedAtom::Variable).collect();
SolvedType::Boolean(SolvedAtom::Variable(free), solved_rest)
fn container(cvar: VarId, mvars: Vec<VarId>) -> SolvedType {
SolvedType::Boolean(SolvedBool::SolvedContainer(cvar, mvars))
}
pub fn uniq_stdlib() -> StdLib {

View file

@ -239,7 +239,8 @@ fn can_annotation_help(
let var_name = Lowercase::from(ident);
if let Some(var) = introduced_variables.var_by_name(&var_name) {
vars.push((var_name, Type::Variable(*var)));
vars.push((var_name.clone(), Type::Variable(*var)));
lowercase_vars.push(Located::at(loc_var.region, (var_name, *var)));
} else {
let var = var_store.fresh();
@ -278,11 +279,15 @@ fn can_annotation_help(
let alias = Alias {
region,
vars: lowercase_vars,
typ: alias_actual.clone(),
uniqueness: None,
typ: alias_actual,
};
local_aliases.insert(symbol, alias);
Type::Alias(symbol, vars, Box::new(alias_actual))
// We turn this 'inline' alias into an Apply. This will later get de-aliased again,
// but this approach is easier wrt. instantiation of uniqueness variables.
let args = vars.into_iter().map(|(_, b)| b).collect();
Type::Apply(symbol, args)
}
_ => {
// This is a syntactically invalid type alias.

View file

@ -168,7 +168,15 @@ pub fn canonicalize_defs<'a>(
pattern_type,
)
} else {
panic!("TODO gracefully handle the case where a type annotation appears immediately before a body def, but the patterns are different. This should be an error; put a newline or comment between them!");
// the pattern of the annotation does not match the pattern of the body directly below it
env.problems.push(Problem::SignatureDefMismatch {
annotation_pattern: pattern.region,
def_pattern: body_pattern.region,
});
// both the annotation and definition are skipped!
iter.next();
continue;
}
}
_ => to_pending_def(env, var_store, &loc_def.value, &mut scope, pattern_type),
@ -243,6 +251,7 @@ pub fn canonicalize_defs<'a>(
let alias = roc_types::types::Alias {
region: ann.region,
vars: can_vars,
uniqueness: None,
typ: can_ann.typ,
};
aliases.insert(symbol, alias);

View file

@ -3,7 +3,7 @@ use inlinable_string::InlinableString;
use roc_collections::all::{MutMap, MutSet};
use roc_module::symbol::{IdentIds, ModuleId, ModuleIds, Symbol};
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::Region;
use roc_region::all::{Located, Region};
/// The canonicalization environment for a particular module.
pub struct Env<'a> {
@ -70,23 +70,47 @@ impl<'a> Env<'a> {
Some(&module_id) => {
let ident: InlinableString = ident.into();
match self
.dep_idents
.get(&module_id)
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
{
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
// You can do qualified lookups on your own module, e.g.
// if I'm in the Foo module, I can do a `Foo.bar` lookup.
if module_id == self.home {
match self.ident_ids.get_id(&ident) {
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
self.referenced_symbols.insert(symbol);
self.referenced_symbols.insert(symbol);
Ok(symbol)
Ok(symbol)
}
None => Err(RuntimeError::LookupNotInScope(
Located {
value: ident,
region,
},
self.ident_ids
.idents()
.map(|(_, string)| string.as_ref().into())
.collect(),
)),
}
} else {
match self
.dep_idents
.get(&module_id)
.and_then(|exposed_ids| exposed_ids.get_id(&ident))
{
Some(ident_id) => {
let symbol = Symbol::new(module_id, *ident_id);
self.referenced_symbols.insert(symbol);
Ok(symbol)
}
None => Err(RuntimeError::ValueNotExposed {
module_name,
ident,
region,
}),
}
None => Err(RuntimeError::ValueNotExposed {
module_name,
ident,
region,
}),
}
}
None => Err(RuntimeError::ModuleNotImported {

View file

@ -146,6 +146,14 @@ impl Scope {
vars: Vec<Located<(Lowercase, Variable)>>,
typ: Type,
) {
self.aliases.insert(name, Alias { region, vars, typ });
self.aliases.insert(
name,
Alias {
region,
vars,
uniqueness: None,
typ,
},
);
}
}

View file

@ -6,8 +6,8 @@ use roc_collections::all::{ImMap, MutMap, MutSet, SendMap};
use roc_module::ident::Lowercase;
use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::Located;
use roc_types::boolean_algebra::{Atom, Bool};
use roc_types::solved_types::{BuiltinAlias, SolvedAtom, SolvedType};
use roc_types::boolean_algebra::Bool;
use roc_types::solved_types::{BuiltinAlias, SolvedBool, SolvedType};
use roc_types::subs::{VarId, VarStore, Variable};
use roc_types::types::{Alias, Type};
@ -150,6 +150,7 @@ where
let alias = Alias {
vars,
region: builtin_alias.region,
uniqueness: None,
typ: actual,
};
@ -206,16 +207,7 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut V
Type::Variable(var)
}
}
Flex(var_id) => {
if let Some(var) = free_vars.unnamed_vars.get(&var_id) {
Type::Variable(*var)
} else {
let var = var_store.fresh();
free_vars.unnamed_vars.insert(*var_id, var);
Type::Variable(var)
}
}
Flex(var_id) => Type::Variable(var_id_to_flex_var(*var_id, free_vars, var_store)),
Wildcard => {
let var = var_store.fresh();
free_vars.wildcards.push(var);
@ -271,15 +263,15 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut V
Box::new(to_type(ext, free_vars, var_store)),
)
}
Boolean(solved_free, solved_rest) => {
let free = to_atom(solved_free, free_vars, var_store);
let mut rest = Vec::with_capacity(solved_rest.len());
Boolean(SolvedBool::SolvedShared) => Type::Boolean(Bool::Shared),
Boolean(SolvedBool::SolvedContainer(solved_cvar, solved_mvars)) => {
let cvar = var_id_to_flex_var(*solved_cvar, free_vars, var_store);
for solved_atom in solved_rest {
rest.push(to_atom(solved_atom, free_vars, var_store));
}
let mvars = solved_mvars
.iter()
.map(|var_id| var_id_to_flex_var(*var_id, free_vars, var_store));
Type::Boolean(Bool::from_parts(free, rest))
Type::Boolean(Bool::container(cvar, mvars))
}
Alias(symbol, solved_type_variables, solved_actual) => {
let mut type_variables = Vec::with_capacity(solved_type_variables.len());
@ -297,24 +289,18 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut V
}
}
pub fn to_atom(
solved_atom: &SolvedAtom,
fn var_id_to_flex_var(
var_id: VarId,
free_vars: &mut FreeVars,
var_store: &mut VarStore,
) -> Atom {
match solved_atom {
SolvedAtom::Zero => Atom::Zero,
SolvedAtom::One => Atom::One,
SolvedAtom::Variable(var_id) => {
if let Some(var) = free_vars.unnamed_vars.get(&var_id) {
Atom::Variable(*var)
} else {
let var = var_store.fresh();
free_vars.unnamed_vars.insert(*var_id, var);
) -> Variable {
if let Some(var) = free_vars.unnamed_vars.get(&var_id) {
*var
} else {
let var = var_store.fresh();
free_vars.unnamed_vars.insert(var_id, var);
Atom::Variable(var)
}
}
var
}
}
@ -347,6 +333,7 @@ pub fn constrain_imported_aliases(
let alias = Alias {
vars,
region: imported_alias.region,
uniqueness: imported_alias.uniqueness,
typ: actual,
};

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,10 +248,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 record_type = attr_type(
@ -305,10 +302,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 +1246,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 +1303,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 +1385,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 +1409,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),
),
)
}
}
@ -1651,7 +1691,7 @@ fn constrain_def_pattern(
fn annotation_to_attr_type(
var_store: &mut VarStore,
ann: &Type,
rigids: &mut ImMap<Variable, Variable>,
rigids: &mut ImSet<Variable>,
change_var_kind: bool,
) -> (Vec<Variable>, Type) {
use roc_types::types::Type::*;
@ -1659,19 +1699,12 @@ fn annotation_to_attr_type(
match ann {
Variable(var) => {
if change_var_kind {
if let Some(uvar) = rigids.get(var) {
(
vec![],
attr_type(Bool::variable(*uvar), Type::Variable(*var)),
)
} else {
let uvar = var_store.fresh();
rigids.insert(*var, uvar);
(
vec![],
attr_type(Bool::variable(uvar), Type::Variable(*var)),
)
}
let uvar = var_store.fresh();
rigids.insert(uvar);
(
vec![],
attr_type(Bool::variable(uvar), Type::Variable(*var)),
)
} else {
(vec![], Type::Variable(*var))
}
@ -1711,9 +1744,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),
@ -1782,26 +1813,46 @@ 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()),
);
(vars, result)
}
Alias(symbol, fields, actual) => {
@ -1840,7 +1891,7 @@ fn annotation_to_attr_type(
fn annotation_to_attr_type_many(
var_store: &mut VarStore,
anns: &[Type],
rigids: &mut ImMap<Variable, Variable>,
rigids: &mut ImSet<Variable>,
change_var_kind: bool,
) -> (Vec<Variable>, Vec<Type>) {
anns.iter()
@ -1866,14 +1917,23 @@ 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);
let (_, new) = annotation_to_attr_type(var_store, &alias.typ, &mut ImSet::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();
if let Type::Boolean(b) = args[0].clone() {
alias.uniqueness = Some(b);
}
}
_ => unreachable!(),
_ => unreachable!("`annotation_to_attr_type` always gives back an Attr"),
}
// Check that if the alias is a recursive tag union, all structures containing the
// recursion variable get the same uniqueness as the recursion variable (and thus as the
// recursive tag union itself)
if let Some(b) = &alias.uniqueness {
fix_mutual_recursive_alias(&mut alias.typ, b);
}
}
}
@ -2009,9 +2069,9 @@ fn instantiate_rigids(
annotation.substitute(&rigid_substitution);
}
let mut new_rigid_pairs = ImMap::default();
let mut new_uniqueness_rigids = ImSet::default();
let (mut uniq_vars, annotation) =
annotation_to_attr_type(var_store, &annotation, &mut new_rigid_pairs, true);
annotation_to_attr_type(var_store, &annotation, &mut new_uniqueness_rigids, true);
if let Pattern::Identifier(symbol) = loc_pattern.value {
headers.insert(symbol, Located::at(loc_pattern.region, annotation.clone()));
@ -2021,7 +2081,7 @@ fn instantiate_rigids(
) {
for (k, v) in new_headers {
let (new_uniq_vars, attr_annotation) =
annotation_to_attr_type(var_store, &v.value, &mut new_rigid_pairs, true);
annotation_to_attr_type(var_store, &v.value, &mut new_uniqueness_rigids, true);
uniq_vars.extend(new_uniq_vars);
@ -2031,10 +2091,7 @@ fn instantiate_rigids(
new_rigids.extend(uniq_vars);
new_rigids.extend(introduced_vars.wildcards.iter().cloned());
for (_, v) in new_rigid_pairs {
new_rigids.push(v);
}
new_rigids.extend(new_uniqueness_rigids);
annotation
}
@ -2245,3 +2302,85 @@ fn constrain_field_update(
(var, field_type, con)
}
/// Fix uniqueness attributes on mutually recursive type aliases.
/// Given aliases
///
/// > ListA a b : [ Cons a (ListB b a), Nil ]
/// > ListB a b : [ Cons a (ListA b a), Nil ]
///
/// We get the lifted alias:
///
/// > `Test.ListB`: Alias {
/// > ...,
/// > uniqueness: Some(
/// > Container(
/// > 118,
/// > {},
/// > ),
/// > ),
/// > typ: [ Global('Cons') <9> (`#Attr.Attr` Container(119, {}) Alias `Test.ListA` <10> <9>[ but actually [ Global('Cons') <10> (`#Attr.Attr` Container(118, {}) <13>), Global('Nil') ] ]), Global('Nil') ] as <13>,
/// > },
///
/// Note that the alias will get uniqueness variable <118>, but the contained `ListA` gets variable
/// <119>. But, 119 is contained in 118, and 118 in 119, so we need <119> >= <118> >= <119> >= <118> ...
/// That can only be true if they are the same. Type inference will not find that, so we must do it
/// ourselves in user-defined aliases.
fn fix_mutual_recursive_alias(typ: &mut Type, attribute: &Bool) {
use Type::*;
if let RecursiveTagUnion(rec, tags, _ext) = typ {
for (_, args) in tags {
for mut arg in args {
fix_mutual_recursive_alias_help(*rec, &Type::Boolean(attribute.clone()), &mut arg);
}
}
}
}
fn fix_mutual_recursive_alias_help(rec_var: Variable, attribute: &Type, into_type: &mut Type) {
if into_type.contains_variable(rec_var) {
if let Type::Apply(Symbol::ATTR_ATTR, args) = into_type {
std::mem::replace(&mut args[0], attribute.clone());
fix_mutual_recursive_alias_help_help(rec_var, attribute, &mut args[1]);
}
}
}
#[inline(always)]
fn fix_mutual_recursive_alias_help_help(rec_var: Variable, attribute: &Type, into_type: &mut Type) {
use Type::*;
match into_type {
Function(args, ret) => {
fix_mutual_recursive_alias_help(rec_var, attribute, ret);
args.iter_mut()
.for_each(|arg| fix_mutual_recursive_alias_help(rec_var, attribute, arg));
}
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
fix_mutual_recursive_alias_help(rec_var, attribute, ext);
for (_tag, args) in tags.iter_mut() {
for arg in args.iter_mut() {
fix_mutual_recursive_alias_help(rec_var, attribute, arg);
}
}
}
Record(fields, ext) => {
fix_mutual_recursive_alias_help(rec_var, attribute, ext);
fields
.iter_mut()
.for_each(|arg| fix_mutual_recursive_alias_help(rec_var, attribute, arg));
}
Alias(_, _, actual_type) => {
// call help_help, because actual_type is not wrapped in ATTR
fix_mutual_recursive_alias_help_help(rec_var, attribute, actual_type);
}
Apply(_, args) => {
args.iter_mut()
.for_each(|arg| fix_mutual_recursive_alias_help(rec_var, attribute, arg));
}
EmptyRec | EmptyTagUnion | Erroneous(_) | Variable(_) | Boolean(_) => {}
}
}

View file

@ -1398,6 +1398,7 @@ fn call_with_args<'a, 'ctx, 'env>(
Symbol::FLOAT_ROUND => call_intrinsic(LLVM_LROUND_I64_F64, env, args),
Symbol::LIST_SET => list_set(parent, args, env, InPlace::Clone),
Symbol::LIST_SET_IN_PLACE => list_set(parent, args, env, InPlace::InPlace),
Symbol::LIST_PUSH => list_push(args, env),
Symbol::LIST_SINGLE => {
// List.single : a -> List a
debug_assert!(args.len() == 1);
@ -1918,6 +1919,100 @@ fn bounds_check_comparison<'ctx>(
builder.build_int_compare(IntPredicate::ULT, elem_index, len, "bounds_check")
}
fn list_push<'a, 'ctx, 'env>(
args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)],
env: &Env<'a, 'ctx, 'env>,
) -> BasicValueEnum<'ctx> {
// List.push List elem, elem -> List elem
let builder = env.builder;
let ctx = env.context;
debug_assert!(args.len() == 2);
let original_wrapper = args[0].0.into_struct_value();
// Load the usize length from the wrapper.
let list_len = load_list_len(builder, original_wrapper);
let (elem, elem_layout) = args[1];
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
let ptr_type = get_ptr_type(&elem_type, AddressSpace::Generic);
let elems_ptr = load_list_ptr(builder, original_wrapper, ptr_type);
// The output list length, which is the old list length + 1
let new_list_len = env.builder.build_int_add(
ctx.i64_type().const_int(1 as u64, false),
list_len,
"new_list_length",
);
let ctx = env.context;
let ptr_bytes = env.ptr_bytes;
// Calculate the number of bytes we'll need to allocate.
let elem_bytes = env
.ptr_int()
.const_int(elem_layout.stack_size(env.ptr_bytes) as u64, false);
// This is the size of the list coming in, before we have added an element
// to the end.
let list_size = env
.builder
.build_int_mul(elem_bytes, list_len, "mul_old_len_by_elem_bytes");
// Allocate space for the new array that we'll copy into.
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
let clone_ptr = builder
.build_array_malloc(elem_type, new_list_len, "list_ptr")
.unwrap();
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(clone_ptr, int_type, "list_cast_ptr");
// TODO check if malloc returned null; if so, runtime error for OOM!
if elem_layout.safe_to_memcpy() {
// Copy the bytes from the original array into the new
// one we just malloc'd.
//
// TODO how do we decide when to do the small memcpy vs the normal one?
builder.build_memcpy(clone_ptr, ptr_bytes, elems_ptr, ptr_bytes, list_size);
} else {
panic!("TODO Cranelift currently only knows how to clone list elements that are Copy.");
}
// Create a fresh wrapper struct for the newly populated array
let struct_type = collection(ctx, env.ptr_bytes);
let mut struct_val;
// Store the pointer
struct_val = builder
.build_insert_value(
struct_type.get_undef(),
ptr_as_int,
Builtin::WRAPPER_PTR,
"insert_ptr",
)
.unwrap();
// Store the length
struct_val = builder
.build_insert_value(struct_val, new_list_len, Builtin::WRAPPER_LEN, "insert_len")
.unwrap();
let answer = builder.build_bitcast(
struct_val.into_struct_value(),
collection(ctx, ptr_bytes),
"cast_collection",
);
let elem_ptr = unsafe { builder.build_in_bounds_gep(clone_ptr, &[list_len], "load_index") };
builder.build_store(elem_ptr, elem);
answer
}
fn list_set<'a, 'ctx, 'env>(
parent: FunctionValue<'ctx>,
args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)],

View file

@ -489,10 +489,17 @@ mod gen_builtins {
);
}
// #[test]
// fn list_push() {
// assert_evals_to!("List.push [] 1", &[1], &'static [i64]);
// }
#[test]
fn list_push() {
assert_evals_to!("List.push [1] 2", &[1, 2], &'static [i64]);
assert_evals_to!("List.push [1, 1] 2", &[1, 1, 2], &'static [i64]);
assert_evals_to!("List.push [] 3", &[3], &'static [i64]);
assert_evals_to!(
"List.push [ True, False ] True",
&[true, false, true],
&'static [bool]
);
}
#[test]
fn list_single() {

View file

@ -12,7 +12,7 @@ alwaysThree = \_ -> "foo"
identity = \a -> a
z = identity (alwaysThree {})
z = identity (Primary.alwaysThree {})
w : Dep1.Identity {}
w = Identity {}

View file

@ -220,12 +220,12 @@ mod test_uniq_load {
expect_types(
loaded_module,
hashmap! {
"findPath" => "Attr * (Attr * { costFunction : (Attr Shared (Attr Shared position, Attr Shared position -> Attr Shared Float)), end : (Attr Shared position), moveFunction : (Attr Shared (Attr Shared position -> Attr * (Set (Attr Shared position)))), start : (Attr Shared position) } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))",
"findPath" => "Attr * (Attr * { costFunction : (Attr Shared (Attr Shared position, Attr Shared position -> Attr Shared Float)), end : (Attr Shared position), moveFunction : (Attr Shared (Attr Shared position -> Attr * (Set (Attr * position)))), start : (Attr Shared position) } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))",
"initialModel" => "Attr * (Attr Shared position -> Attr * (Model (Attr Shared position)))",
"reconstructPath" => "Attr Shared (Attr Shared (Map (Attr Shared position) (Attr Shared position)), Attr Shared position -> Attr * (List (Attr Shared position)))",
"reconstructPath" => "Attr Shared (Attr Shared (Map (Attr * position) (Attr Shared position)), Attr Shared position -> Attr * (List (Attr Shared position)))",
"updateCost" => "Attr * (Attr Shared position, Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr Shared (Model (Attr Shared position)))",
"cheapestOpen" => "Attr * (Attr * (Attr Shared position -> Attr Shared Float), Attr * (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))",
"astar" => "Attr Shared (Attr Shared (Attr Shared position, Attr Shared position -> Attr Shared Float), Attr Shared (Attr Shared position -> Attr * (Set (Attr Shared position))), Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr * [ Err (Attr * [ KeyNotFound ]*), Ok (Attr * (List (Attr Shared position))) ]*)",
"cheapestOpen" => "Attr * (Attr * (Attr Shared position -> Attr Shared Float), Attr (* | a | b) (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))",
"astar" => "Attr Shared (Attr Shared (Attr Shared position, Attr Shared position -> Attr Shared Float), Attr Shared (Attr Shared position -> Attr * (Set (Attr * position))), Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr * [ Err (Attr * [ KeyNotFound ]*), Ok (Attr * (List (Attr Shared position))) ]*)",
},
);
});
@ -242,7 +242,7 @@ mod test_uniq_load {
loaded_module,
hashmap! {
"swap" => "Attr * (Attr Shared Int, Attr Shared Int, Attr * (List (Attr Shared a)) -> Attr * (List (Attr Shared a)))",
"partition" => "Attr * (Attr Shared Int, Attr Shared Int, Attr b (List (Attr Shared (Num (Attr Shared a)))) -> Attr * [ Pair (Attr Shared Int) (Attr b (List (Attr Shared (Num (Attr Shared a))))) ])",
"partition" => "Attr * (Attr Shared Int, Attr Shared Int, Attr b (List (Attr Shared (Num (Attr Shared a)))) -> Attr * [ Pair (Attr * Int) (Attr b (List (Attr Shared (Num (Attr Shared a))))) ])",
"quicksort" => "Attr Shared (Attr b (List (Attr Shared (Num (Attr Shared a)))), Attr Shared Int, Attr Shared Int -> Attr b (List (Attr Shared (Num (Attr Shared a)))))",
},
);
@ -284,7 +284,24 @@ mod test_uniq_load {
"w" => "Attr * (Dep1.Identity (Attr * {}))",
"succeed" => "Attr * (Attr b a -> Attr * (Dep1.Identity (Attr b a)))",
"yay" => "Attr * (Res.Res (Attr * {}) (Attr * err))",
"withDefault" => "Attr * (Attr * (Res.Res (Attr a b) (Attr * *)), Attr a b -> Attr a b)",
"withDefault" => "Attr * (Attr (* | a | b) (Res.Res (Attr a c) (Attr b *)), Attr a c -> Attr a c)",
},
);
});
}
#[test]
fn load_custom_res() {
test_async(async {
let subs_by_module = MutMap::default();
let loaded_module = load_fixture("interface_with_deps", "Res", subs_by_module).await;
expect_types(
loaded_module,
hashmap! {
"withDefault" =>"Attr * (Attr (* | b | c) (Res (Attr b a) (Attr c err)), Attr b a -> Attr b a)",
"map" => "Attr * (Attr (* | c | d) (Res (Attr d a) (Attr c err)), Attr * (Attr d a -> Attr e b) -> Attr * (Res (Attr e b) (Attr c err)))",
"andThen" => "Attr * (Attr (* | c | d) (Res (Attr d a) (Attr c err)), Attr * (Attr d a -> Attr e (Res (Attr f b) (Attr c err))) -> Attr e (Res (Attr f b) (Attr c err)))"
},
);
});

View file

@ -46,6 +46,10 @@ pub enum Problem {
replaced_region: Region,
},
RuntimeError(RuntimeError),
SignatureDefMismatch {
annotation_pattern: Region,
def_pattern: Region,
},
}
#[derive(Clone, Debug, PartialEq)]

View file

@ -1,6 +1,7 @@
use roc_collections::all::MutSet;
use roc_problem::can::PrecedenceProblem::BothNonAssociative;
use roc_problem::can::{Problem, RuntimeError};
use roc_region::all::Region;
use std::path::PathBuf;
use crate::report::{Annotation, Report, RocDocAllocator, RocDocBuilder};
@ -238,6 +239,14 @@ pub fn can_problem<'b>(
alloc.reflow(" definitions from this tag union type."),
]),
]),
Problem::SignatureDefMismatch {
ref annotation_pattern,
ref def_pattern,
} => alloc.stack(vec![
alloc.reflow("This annotation does not match the definition immediately following it:"),
alloc.region(Region::span_across(annotation_pattern, def_pattern)),
alloc.reflow("Is it a typo? If not, put either a newline or comment between them."),
]),
Problem::RuntimeError(runtime_error) => pretty_runtime_error(alloc, runtime_error),
};

View file

@ -2684,6 +2684,34 @@ mod test_reporting {
)
}
#[test]
fn annotation_definition_mismatch() {
report_problem_as(
indoc!(
r#"
bar : Int
foo = \x -> x
# NOTE: neither bar or foo are defined at this point
4
"#
),
indoc!(
r#"
-- SYNTAX PROBLEM --------------------------------------------------------------
This annotation does not match the definition immediately following
it:
1 > bar : Int
2 > foo = \x -> x
Is it a typo? If not, put either a newline or comment between them.
"#
),
)
}
#[test]
fn invalid_num() {
report_problem_as(
@ -2842,4 +2870,80 @@ mod test_reporting {
),
)
}
#[test]
fn two_different_cons() {
report_problem_as(
indoc!(
r#"
ConsList a : [ Cons a (ConsList a), Nil ]
x : ConsList {}
x = Cons {} (Cons "foo" Nil)
x
"#
),
indoc!(
r#"
-- TYPE MISMATCH ---------------------------------------------------------------
Something is off with the body of the `x` definition:
3 x : ConsList {}
4 x = Cons {} (Cons "foo" Nil)
^^^^^^^^^^^^^^^^^^^^^^^^
This `Cons` global tag application has the type:
[ Cons {} [ Cons Str [ Cons {} a, Nil ] as a, Nil ], Nil ]
But the type annotation on `x` says it should be:
[ Cons {} a, Nil ] as a
"#
),
)
}
#[test]
fn mutually_recursive_types_with_type_error() {
report_problem_as(
indoc!(
r#"
AList a b : [ ACons a (BList a b), ANil ]
BList a b : [ BCons a (AList a b), BNil ]
x : AList Int Int
x = ACons 0 (BCons 1 (ACons "foo" BNil ))
y : BList a a
y = BNil
{ x, y }
"#
),
indoc!(
r#"
-- TYPE MISMATCH ---------------------------------------------------------------
Something is off with the body of the `x` definition:
4 x : AList Int Int
5 x = ACons 0 (BCons 1 (ACons "foo" BNil ))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This `ACons` global tag application has the type:
[ ACons (Num Integer) [ BCons (Num Integer) [ ACons Str [
BCons Int [ ACons Int (BList Int Int), ANil ] as a, BNil ], ANil
], BNil ], ANil ]
But the type annotation on `x` says it should be:
[ ACons Int (BList Int Int), ANil ] as a
"#
),
)
}
}

View file

@ -4,7 +4,7 @@ use roc_collections::all::{ImMap, MutMap, SendMap};
use roc_module::ident::TagName;
use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region};
use roc_types::boolean_algebra::{self, Atom};
use roc_types::boolean_algebra::{self, Bool};
use roc_types::solved_types::Solved;
use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable};
use roc_types::types::Type::{self, *};
@ -547,7 +547,7 @@ fn type_to_variable(
EmptyTagUnion => Variable::EMPTY_TAG_UNION,
// This case is important for the rank of boolean variables
Boolean(boolean_algebra::Bool(Atom::Variable(var), rest)) if rest.is_empty() => *var,
Boolean(boolean_algebra::Bool::Container(cvar, mvars)) if mvars.is_empty() => *cvar,
Boolean(b) => {
let content = Content::Structure(FlatType::Boolean(b.clone()));
@ -650,6 +650,7 @@ fn type_to_variable(
}
Alias(Symbol::BOOL_BOOL, _, _) => Variable::BOOL,
Alias(symbol, args, alias_type) => {
// TODO cache in uniqueness inference gives problems! all Int's get the same uniqueness var!
// Cache aliases without type arguments. Commonly used aliases like `Int` would otherwise get O(n)
// different variables (once for each occurence). The recursion restriction is required
// for uniqueness types only: recursive aliases "introduce" an unbound uniqueness
@ -666,11 +667,13 @@ fn type_to_variable(
// TODO does caching work at all with uniqueness types? even Int then hides a uniqueness variable
let is_recursive = alias_type.is_recursive();
let no_args = args.is_empty();
/*
if no_args && !is_recursive {
if let Some(var) = cached.get(symbol) {
return *var;
}
}
*/
let mut arg_vars = Vec::with_capacity(args.len());
let mut new_aliases = ImMap::default();
@ -688,7 +691,7 @@ fn type_to_variable(
let result = register(subs, rank, pools, content);
if no_args && !is_recursive {
cached.insert(*symbol, result);
// cached.insert(*symbol, result);
}
result
@ -793,7 +796,30 @@ fn check_for_infinite_type(
_ => circular_error(subs, problems, symbol, &loc_var),
}
}
_ => circular_error(subs, problems, symbol, &loc_var),
Content::Structure(FlatType::Boolean(Bool::Container(_cvar, _mvars))) => {
// We have a loop in boolean attributes. The attributes can be seen as constraints
// too, so if we have
//
// Container( u1, { u2, u3 } )
//
// That means u1 >= u2 and u1 >= u3
//
// Now if u1 occurs in the definition of u2, then that's like saying u1 >= u2 >= u1,
// which can only be true if u1 == u2. So that's what we do with unify.
for var in chain {
if let Content::Structure(FlatType::Boolean(_)) =
subs.get_without_compacting(var).content
{
// this unify just makes new pools. is that bad?
let outcome = unify(subs, recursive, var);
debug_assert!(matches!(outcome, roc_unify::unify::Unified::Success(_)));
}
}
boolean_algebra::flatten(subs, recursive);
}
_other => circular_error(subs, problems, symbol, &loc_var),
}
}
}
@ -1050,9 +1076,11 @@ fn adjust_rank_content(
rank
}
Boolean(b) => {
let mut rank = Rank::toplevel();
for var in b.variables() {
Boolean(Bool::Shared) => Rank::toplevel(),
Boolean(Bool::Container(cvar, mvars)) => {
let mut rank = adjust_rank(subs, young_mark, visit_mark, group_rank, cvar);
for var in mvars {
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
}

View file

@ -2490,31 +2490,37 @@ mod test_solve {
);
}
#[test]
fn wrapper() {
// based on https://github.com/elm/compiler/issues/1964
// Roc seems to do this correctly, tracking to make sure it stays that way
infer_eq_without_problem(
indoc!(
r#"
Type a : [ TypeCtor (Type (Wrapper a)) ]
Wrapper a : [ Wrapper a ]
Opaque : [ Opaque ]
encodeType1 : Type a -> Opaque
encodeType1 = \thing ->
when thing is
TypeCtor v0 ->
encodeType1 v0
encodeType1
"#
),
"Type a -> Opaque",
);
}
// Like in elm, this test now fails. Polymorphic recursion (even with an explicit signature)
// yields a type error.
//
// We should at some point investigate why that is. Elm did support polymorphic recursion in
// earlier versions.
//
// #[test]
// fn wrapper() {
// // based on https://github.com/elm/compiler/issues/1964
// // Roc seems to do this correctly, tracking to make sure it stays that way
// infer_eq_without_problem(
// indoc!(
// r#"
// Type a : [ TypeCtor (Type (Wrapper a)) ]
//
// Wrapper a : [ Wrapper a ]
//
// Opaque : [ Opaque ]
//
// encodeType1 : Type a -> Opaque
// encodeType1 = \thing ->
// when thing is
// TypeCtor v0 ->
// encodeType1 v0
//
// encodeType1
// "#
// ),
// "Type a -> Opaque",
// );
// }
#[test]
fn rigids() {

View file

@ -54,7 +54,10 @@ mod test_uniq_solve {
let (problems, actual) = infer_eq_help(src);
if !problems.is_empty() {
panic!("expected:\n{:?}\ninferred:\n{:?}", expected, actual);
panic!(
"expected:\n{:?}\ninferred:\n{:?}\nproblems:\n{:?}",
expected, actual, problems
);
}
assert_eq!(actual, expected.to_string());
@ -1080,7 +1083,7 @@ mod test_uniq_solve {
// TODO: is it safe to ignore uniqueness constraints from patterns that bind no identifiers?
// i.e. the `b` could be ignored in this example, is that true in general?
// seems like it because we don't really extract anything.
"Attr * (Attr (* | a | b) [ Foo (Attr a c) (Attr b *) ]* -> Attr * [ Foo (Attr a c) (Attr * Str) ]*)"
"Attr * (Attr (* | a | b) [ Foo (Attr b c) (Attr a *) ]* -> Attr * [ Foo (Attr b c) (Attr * Str) ]*)"
);
}
@ -1303,7 +1306,7 @@ mod test_uniq_solve {
r
"#
),
"Attr * (Attr (a | b) { foo : (Attr a { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f -> Attr (a | b) { foo : (Attr a { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f)"
"Attr * (Attr (a | b) { foo : (Attr b { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f -> Attr (a | b) { foo : (Attr b { bar : (Attr Shared d), baz : (Attr Shared c) }e) }f)"
);
}
@ -1321,7 +1324,7 @@ mod test_uniq_solve {
r
"#
),
"Attr * (Attr (a | b) { foo : (Attr a { bar : (Attr Shared c) }d) }e -> Attr (a | b) { foo : (Attr a { bar : (Attr Shared c) }d) }e)"
"Attr * (Attr (a | b) { foo : (Attr b { bar : (Attr Shared c) }d) }e -> Attr (a | b) { foo : (Attr b { bar : (Attr Shared c) }d) }e)"
);
}
@ -1356,8 +1359,7 @@ mod test_uniq_solve {
r.tic.tac.toe
"#
),
"Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (a | b | e) { bar : (Attr (a | e) { baz : (Attr e f) }*) }*), tic : (Attr (c | d | e) { tac : (Attr (d | e) { toe : (Attr e f) }*) }*) }* -> Attr e f)"
// "Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (a | b | c) { bar : (Attr (a | c) { baz : (Attr c f) }*) }*), tic : (Attr (c | d | e) { tac : (Attr (c | d) { toe : (Attr c f) }*) }*) }* -> Attr c f)"
"Attr * (Attr (* | a | b | c | d | e) { foo : (Attr (d | b | e) { bar : (Attr (e | b) { baz : (Attr b f) }*) }*), tic : (Attr (a | b | c) { tac : (Attr (c | b) { toe : (Attr b f) }*) }*) }* -> Attr b f)"
);
}
@ -1623,10 +1625,10 @@ mod test_uniq_solve {
infer_eq(
indoc!(
r#"
singleton : p -> [ Cons p (ConsList p), Nil ] as ConsList p
singleton = \x -> Cons x Nil
singleton : p -> [ Cons p (ConsList p), Nil ] as ConsList p
singleton = \x -> Cons x Nil
singleton
singleton
"#
),
"Attr * (Attr a p -> Attr * (ConsList (Attr a p)))",
@ -1668,7 +1670,7 @@ mod test_uniq_solve {
map
"#
),
"Attr Shared (Attr Shared (Attr a p -> Attr b q), Attr * (ConsList (Attr a p)) -> Attr * (ConsList (Attr b q)))" ,
"Attr Shared (Attr Shared (Attr a p -> Attr b q), Attr (* | a) (ConsList (Attr a p)) -> Attr * (ConsList (Attr b q)))" ,
);
}
@ -1690,7 +1692,7 @@ mod test_uniq_solve {
map
"#
),
"Attr Shared (Attr Shared (Attr a b -> c), Attr d [ Cons (Attr a b) (Attr d e), Nil ]* as e -> Attr f [ Cons c (Attr f g), Nil ]* as g)" ,
"Attr Shared (Attr Shared (Attr a b -> c), Attr (d | a) [ Cons (Attr a b) (Attr (d | a) e), Nil ]* as e -> Attr f [ Cons c (Attr f g), Nil ]* as g)" ,
);
}
@ -1735,8 +1737,6 @@ mod test_uniq_solve {
);
}
// This snippet exhibits the rank issue. Seems to only occur when using recursive types with
// recursive functions.
#[test]
fn rigids_in_signature() {
infer_eq(
@ -1744,13 +1744,19 @@ mod test_uniq_solve {
r#"
ConsList a : [ Cons a (ConsList a), Nil ]
map : (p -> q), p -> ConsList q
map = \f, x -> map f x
map : (p -> q), ConsList p -> ConsList q
map = \f, list ->
when list is
Cons x xs ->
Cons (f x) (map f xs)
Nil ->
Nil
map
"#
),
"Attr Shared (Attr * (Attr a p -> Attr b q), Attr a p -> Attr * (ConsList (Attr b q)))",
"Attr Shared (Attr Shared (Attr a p -> Attr b q), Attr (* | a) (ConsList (Attr a p)) -> Attr * (ConsList (Attr b q)))",
);
}
@ -1771,7 +1777,7 @@ mod test_uniq_solve {
toEmpty
"#
),
"Attr * (Attr * (ConsList (Attr a p)) -> Attr * (ConsList (Attr a p)))",
"Attr * (Attr * (ConsList (Attr * p)) -> Attr * (ConsList (Attr * p)))",
);
}
@ -1792,7 +1798,7 @@ mod test_uniq_solve {
toEmpty
"#
),
"Attr Shared (Attr * (ConsList (Attr a p)) -> Attr * (ConsList (Attr a p)))",
"Attr Shared (Attr * (ConsList (Attr * p)) -> Attr * (ConsList (Attr * p)))",
);
}
@ -1828,6 +1834,149 @@ mod test_uniq_solve {
);
}
#[test]
fn alias_of_alias() {
infer_eq(
indoc!(
r#"
Foo : { x : Str, y : Float }
Bar : Foo
f : Bar -> Str
f = \{ x } -> x
f
"#
),
"Attr * (Attr (* | a) Bar -> Attr a Str)",
);
}
#[test]
fn alias_of_alias_with_type_variable() {
infer_eq(
indoc!(
r#"
Identity a : [ Identity a ]
ID a : Identity a
f : ID a -> a
f = \Identity x -> x
f
"#
),
"Attr * (Attr (* | b) (ID (Attr b a)) -> Attr b a)",
);
}
#[test]
fn alias_assoc_list_head() {
infer_eq(
indoc!(
r#"
ConsList a : [ Cons a (ConsList a), Nil ]
AssocList a b : ConsList { key: a, value : b }
Maybe a : [ Just a, Nothing ]
# AssocList2 a b : [ Cons { key: a, value : b } (AssocList2 a b), Nil ]
head : AssocList k v -> Maybe { key: k , value: v }
head = \alist ->
when alist is
Cons first _ ->
Just first
Nil ->
Nothing
head
"#
),
"Attr * (Attr (* | a) (AssocList (Attr b k) (Attr c v)) -> Attr * (Maybe (Attr a { key : (Attr b k), value : (Attr c v) })))"
);
}
#[test]
fn cons_list_as_assoc_list_head() {
infer_eq(
indoc!(
r#"
ConsList a : [ Cons a (ConsList a), Nil ]
Maybe a : [ Just a, Nothing ]
head : ConsList { key: k, value: v } -> Maybe { key: k , value: v }
head = \alist ->
when alist is
Cons first _ ->
Just first
Nil ->
Nothing
head
"#
),
"Attr * (Attr (* | a) (ConsList (Attr a { key : (Attr c k), value : (Attr b v) })) -> Attr * (Maybe (Attr a { key : (Attr c k), value : (Attr b v) })))"
);
}
#[test]
fn assoc_list_map() {
infer_eq(
indoc!(
r#"
ConsList a : [ Cons a (ConsList a), Nil ]
map : ConsList a -> ConsList a
map = \list ->
when list is
Cons r xs -> Cons r xs
Nil -> Nil
map
"#
),
"Attr * (Attr (b | c) (ConsList (Attr c a)) -> Attr (b | c) (ConsList (Attr c a)))",
);
}
#[test]
fn same_uniqueness_builtin_list() {
infer_eq(
indoc!(
r#"
toAs : (q -> p), p, q -> List p
toAs =
\f, x, y -> [ x, (f y) ]
toAs
"#
),
"Attr * (Attr * (Attr a q -> Attr b p), Attr b p, Attr a q -> Attr * (List (Attr b p)))"
);
}
#[test]
fn same_uniqueness_cons_list() {
infer_eq(
indoc!(
r#"
ConsList q : [ Cons q (ConsList q), Nil ]
toAs : (q -> p), p, q -> ConsList p
toAs =
\f, x, y ->
# Cons (f y) (Cons x Nil)
Cons x (Cons (f y) Nil)
toAs
"#
),
"Attr * (Attr * (Attr a q -> Attr b p), Attr b p, Attr a q -> Attr * (ConsList (Attr b p)))"
);
}
#[test]
fn typecheck_mutually_recursive_tag_union() {
infer_eq(
@ -1849,10 +1998,48 @@ mod test_uniq_solve {
Cons b newLista ->
Cons a (Cons (f b) (toAs f newLista))
foo = \_ ->
x = 4
{ a : x, b : x }.a
toAs
"#
),
"Attr Shared (Attr Shared (Attr a q -> Attr b p), Attr * (ListA (Attr b p) (Attr a q)) -> Attr * (ConsList (Attr b p)))"
"Attr Shared (Attr Shared (Attr a q -> Attr b p), Attr (* | a | b) (ListA (Attr b p) (Attr a q)) -> Attr * (ConsList (Attr b p)))"
);
}
#[test]
fn typecheck_triple_mutually_recursive_tag_union() {
infer_eq(
indoc!(
r#"
ListA a b : [ Cons a (ListB b a), Nil ]
ListB a b : [ Cons a (ListC b a), Nil ]
ListC a b : [ Cons a (ListA b a), Nil ]
ConsList q : [ Cons q (ConsList q), Nil ]
toAs : (q -> p), ListA p q -> ConsList p
toAs =
\f, lista ->
when lista is
Nil -> Nil
Cons a listb ->
when listb is
Nil -> Nil
Cons b listc ->
when listc is
Nil ->
Nil
Cons c newListA ->
Cons a (Cons (f b) (Cons c (toAs f newListA)))
toAs
"#
),
"Attr Shared (Attr Shared (Attr a q -> Attr b p), Attr (* | a | b) (ListA (Attr b p) (Attr a q)) -> Attr * (ConsList (Attr b p)))"
);
}
@ -1873,7 +2060,7 @@ mod test_uniq_solve {
toAs
"#
),
"Attr Shared (Attr Shared (Attr a b -> c), Attr d [ Cons (Attr e f) (Attr * [ Cons (Attr a b) (Attr d g), Nil ]*), Nil ]* as g -> Attr h [ Cons (Attr e f) (Attr * [ Cons c (Attr h i) ]*), Nil ]* as i)",
"Attr Shared (Attr Shared (Attr a b -> c), Attr (d | a | e) [ Cons (Attr e f) (Attr (d | a | e) [ Cons (Attr a b) (Attr (d | a | e) g), Nil ]*), Nil ]* as g -> Attr h [ Cons (Attr e f) (Attr * [ Cons c (Attr h i) ]*), Nil ]* as i)"
);
}
@ -2242,6 +2429,286 @@ mod test_uniq_solve {
);
}
#[test]
fn list_roc_head() {
infer_eq(
indoc!(
r#"
ConsList a : [ Cons a (ConsList a), Nil ]
Maybe a : [ Just a, Nothing ]
head : ConsList a -> Maybe a
head = \list ->
when list is
Cons x _ -> Just x
Nil -> Nothing
head
"#
),
"Attr * (Attr (* | b) (ConsList (Attr b a)) -> Attr * (Maybe (Attr b a)))",
);
}
#[test]
fn list_roc_is_empty() {
infer_eq(
indoc!(
r#"
ConsList a : [ Cons a (ConsList a), Nil ]
isEmpty : ConsList a -> Bool
isEmpty = \list ->
when list is
Cons _ _ -> False
Nil -> True
isEmpty
"#
),
"Attr * (Attr (* | b) (ConsList (Attr b a)) -> Attr * Bool)",
);
}
#[test]
fn hidden_uniqueness_one() {
infer_eq(
indoc!(
r#"
Model : { foo : Int }
extract : Model -> Int
extract = \{ foo } -> foo
extract
"#
),
"Attr * (Attr (* | a) Model -> Attr a Int)",
);
}
#[test]
fn hidden_uniqueness_two() {
infer_eq(
indoc!(
r#"
Model : { foo : Int, bar : Int }
extract : Model -> Int
extract = \{ foo } -> foo
extract
"#
),
"Attr * (Attr (* | a) Model -> Attr a Int)",
);
}
#[test]
fn hidden_uniqueness_three() {
infer_eq(
indoc!(
r#"
Model : { foo : Int, bar : Int }
# extract : { foo : Int, bar : Int } -> Int
extract : Model -> Int
# extract = \r -> r.foo + r.bar
extract = \{foo, bar} -> foo + bar
extract
"#
),
"Attr * (Attr (* | * | *) Model -> Attr * Int)",
);
}
#[test]
fn peano_roc_is_empty() {
infer_eq(
indoc!(
r#"
Peano : [ Z, S Peano ]
isEmpty : Peano -> Bool
isEmpty = \list ->
when list is
S _ -> False
Z -> True
isEmpty
"#
),
"Attr * (Attr * Peano -> Attr * Bool)",
);
}
#[test]
fn result_roc_map() {
infer_eq(
indoc!(
r#"
map : Result a e, (a -> b) -> Result b e
map = \result, f ->
when result is
Ok v -> Ok (f v)
Err e -> Err e
map
"#
),
"Attr * (Attr (* | c | d) (Result (Attr c a) (Attr d e)), Attr * (Attr c a -> Attr f b) -> Attr * (Result (Attr f b) (Attr d e)))"
);
}
#[test]
fn result_roc_with_default_with_signature() {
infer_eq(
indoc!(
r#"
withDefault : Result a e, a -> a
withDefault = \result, default ->
when result is
Ok v -> v
Err _ -> default
withDefault
"#
),
"Attr * (Attr (* | b | c) (Result (Attr b a) (Attr c e)), Attr b a -> Attr b a)",
);
}
#[test]
fn result_roc_with_default_no_signature() {
infer_eq(
indoc!(
r#"
\result, default ->
when result is
Ok x -> x
Err _ -> default
"#
),
"Attr * (Attr (* | a | b) [ Err (Attr a *), Ok (Attr b c) ]*, Attr b c -> Attr b c)",
);
}
#[test]
fn record_pattern_match_field() {
infer_eq(
indoc!(
r#"
f : { x : b } -> b
f = \{ x } -> x
f
"#
),
"Attr * (Attr (* | a) { x : (Attr a b) } -> Attr a b)",
);
}
#[test]
fn int_addition_with_annotation() {
infer_eq(
indoc!(
r#"
f : Int, Int -> Int
f = \a, b -> a + b
f
"#
),
"Attr * (Attr * Int, Attr * Int -> Attr * Int)",
);
}
#[test]
fn int_abs_with_annotation() {
infer_eq(
indoc!(
r#"
foobar : Int -> Int
foobar = \x -> Num.abs x
foobar
"#
),
"Attr * (Attr * Int -> Attr * Int)",
);
}
#[test]
fn int_addition_without_annotation() {
infer_eq(
indoc!(
r#"
f = \a, b -> a + b + 0x0
f
"#
),
"Attr * (Attr a Int, Attr b Int -> Attr c Int)",
);
}
#[test]
fn num_addition_with_annotation() {
infer_eq(
indoc!(
r#"
f : Num a, Num a -> Num a
f = \a, b -> a + b
f
"#
),
"Attr * (Attr b (Num (Attr b a)), Attr c (Num (Attr c a)) -> Attr d (Num (Attr d a)))",
);
}
#[test]
fn num_addition_without_annotation() {
infer_eq(
indoc!(
r#"
f = \a, b -> a + b
f
"#
),
"Attr * (Attr a (Num (Attr a b)), Attr c (Num (Attr c b)) -> Attr d (Num (Attr d b)))",
);
}
#[test]
fn num_abs_with_annotation() {
infer_eq(
indoc!(
r#"
f : Num a -> Num a
f = \x -> Num.abs x
f
"#
),
"Attr * (Attr b (Num (Attr b a)) -> Attr c (Num (Attr c a)))",
);
}
#[test]
fn num_abs_without_annotation() {
infer_eq(
indoc!(
r#"
\x -> Num.abs x
"#
),
"Attr * (Attr a (Num (Attr a b)) -> Attr c (Num (Attr c b)))",
);
}
#[test]
fn use_correct_ext_var() {
infer_eq(
@ -2278,7 +2745,7 @@ mod test_uniq_solve {
reconstructPath
"#
),
"Attr Shared (Attr Shared (Map (Attr Shared position) (Attr Shared position)), Attr Shared position -> Attr * (List (Attr Shared position)))"
"Attr Shared (Attr Shared (Map (Attr * position) (Attr Shared position)), Attr Shared position -> Attr * (List (Attr Shared position)))"
);
}
@ -2320,7 +2787,7 @@ mod test_uniq_solve {
cheapestOpen
"#
),
"Attr * (Attr * (Attr Shared position -> Attr Shared Float), Attr * (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))"
"Attr * (Attr * (Attr Shared position -> Attr Shared Float), Attr (* | * | *) (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))"
)
});
}
@ -2487,7 +2954,7 @@ mod test_uniq_solve {
findPath
"#
),
"Attr * (Attr * { costFunction : (Attr Shared (Attr Shared position, Attr Shared position -> Attr Shared Float)), end : (Attr Shared position), moveFunction : (Attr Shared (Attr Shared position -> Attr * (Set (Attr Shared position)))), start : (Attr Shared position) } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))"
"Attr * (Attr * { costFunction : (Attr Shared (Attr Shared position, Attr Shared position -> Attr Shared Float)), end : (Attr Shared position), moveFunction : (Attr Shared (Attr Shared position -> Attr * (Set (Attr * position)))), start : (Attr Shared position) } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))"
)
});
}

View file

@ -1,137 +1,182 @@
use self::Atom::*;
use self::Bool::*;
use crate::subs::{Content, FlatType, Subs, Variable};
use roc_collections::all::SendSet;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Bool(pub Atom, pub SendSet<Atom>);
/// Uniqueness types
///
/// Roc uses uniqueness types to optimize programs. Uniqueness inference tries to find values that
/// are guaranteed to be unique (i.e. have a reference count of at most 1) at compile time.
///
/// Such unique values can be updated in-place, something otherwise very unsafe in a pure
/// functional language.
///
/// So how does that all work? Instead of inferring normal types like `Int`, we infer types
/// `Attr u a`. The `a` could be any "normal" type (it's called a base type), like `Int` or `Str`.
/// The `u` is the uniqueness attribute. It stores a value of type `Bool` (see definition below).
///
/// Before doing type inference, variables are tagged as either exclusive or shared. A variable is
/// exclusive if we can be sure it's not duplicated. That's always true when the variable is used
/// just once, but also e.g. `foo.x + foo.y` does not duplicate references to `foo`.
///
/// Next comes actual inference. Variables marked as shared always get the `Shared` uniqueness attribute.
/// For exclusive variables, the uniqueness attribute is initially an unbound type variable.
///
/// An important detail is that there is no `Unique` annotation. Instead, uniqueness variables that
/// are unbound after type inference and monomorphization are interpreted as unique. This makes inference
/// easier and ensures we can never get type errors caused by uniqueness attributes.
///
/// Besides normal type inference rules (e.g. in `f a` if `a : t` then it must be that `f : t -> s`),
/// uniqueness attributes must respect the container rule:
///
/// > Container rule: to extract a unique value from a container, the container must itself be unique
///
/// In this context a container can be a record, tag, built-in data structure (List, Set, etc) or
/// a function closure.
///
/// Thus in the case of `List.get`, it must "check" the container rule. It's type is
///
/// > Attr (Container(w, { u })) (List (Attr u a)), Int -> Result _ _
///
/// The container attribute means that the uniqueness of the container (variable w) is at least
/// uniqueness u. Unique is "more unique" than Shared. So if the elements are unique, the list must be unique. But if the list is
/// unique, the elements can be shared.
///
/// As mentioned, we then use monomorphization to find values that are actually unique (those with
/// an unbound uniqueness attribute). Those values are treated differently. They don't have to be
/// reference counted, and can be mutated in-place.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Copy)]
pub enum Atom {
Zero,
One,
Variable(Variable),
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Bool {
Shared,
Container(Variable, SendSet<Variable>),
}
impl Atom {
pub fn apply_subs(&mut self, subs: &mut Subs) {
match self {
Atom::Zero | Atom::One => {}
Atom::Variable(v) => match subs.get(*v).content {
Content::Structure(FlatType::Boolean(Bool(mut atom, rest))) if rest.is_empty() => {
atom.apply_subs(subs);
*self = atom;
pub fn var_is_shared(subs: &Subs, var: Variable) -> bool {
match subs.get_without_compacting(var).content {
Content::Structure(FlatType::Boolean(Bool::Shared)) => true,
_ => false,
}
}
/// Given the Subs
///
/// 0 |-> Container (Var 1, { Var 2, Var 3 })
/// 1 |-> Flex 'a'
/// 2 |-> Container(Var 4, { Var 5, Var 6 })
/// 3 |-> Flex 'b'
/// 4 |-> Flex 'c'
/// 5 |-> Flex 'd'
/// 6 |-> Shared
///
/// `flatten(subs, Var 0)` will rewrite it to
///
/// 0 |-> Container (Var 1, { Var 4, Var 5, Var 3 })
///
/// So containers are "inlined", and Shared variables are discarded
pub fn flatten(subs: &mut Subs, var: Variable) {
match subs.get_without_compacting(var).content {
Content::Structure(FlatType::Boolean(Bool::Container(cvar, mvars))) => {
let flattened_mvars = var_to_variables(subs, cvar, &mvars);
let content =
Content::Structure(FlatType::Boolean(Bool::Container(cvar, flattened_mvars)));
subs.set_content(var, content);
}
Content::Structure(FlatType::Boolean(Bool::Shared)) => {
// do nothing
}
_ => {
// do nothing
}
}
}
/// For a Container(cvar, start_vars), find (transitively) all the flex/rigid vars that are
/// occur in start_vars.
///
/// Because type aliases in Roc can be recursive, we have to be a bit careful to not get stuck in
/// an infinite loop.
fn var_to_variables(
subs: &Subs,
cvar: Variable,
start_vars: &SendSet<Variable>,
) -> SendSet<Variable> {
let mut stack: Vec<_> = start_vars.into_iter().copied().collect();
let mut seen = SendSet::default();
seen.insert(cvar);
let mut result = SendSet::default();
while let Some(var) = stack.pop() {
if seen.contains(&var) {
continue;
}
seen.insert(var);
match subs.get_without_compacting(var).content {
Content::Structure(FlatType::Boolean(Bool::Container(cvar, mvars))) => {
let it = std::iter::once(cvar).chain(mvars.into_iter());
for v in it {
if !seen.contains(&v) {
stack.push(v);
}
}
_ => {
*self = Atom::Variable(subs.get_root_key(*v));
}
},
}
Content::Structure(FlatType::Boolean(Bool::Shared)) => {
// do nothing
}
_other => {
result.insert(var);
}
}
}
pub fn is_unique(self, subs: &Subs) -> bool {
match self {
Atom::Zero => false,
Atom::One => true,
Atom::Variable(var) => match subs.get_without_compacting(var).content {
Content::Structure(FlatType::Boolean(boolean)) => boolean.is_unique(subs),
// for rank-related reasons, boolean attributes can be "unwrapped" flex vars
Content::FlexVar(_) => true,
_ => false,
},
}
}
result
}
impl Bool {
pub fn shared() -> Self {
Bool(Zero, SendSet::default())
Bool::Shared
}
pub fn unique() -> Self {
Bool(One, SendSet::default())
pub fn container<I>(cvar: Variable, mvars: I) -> Self
where
I: IntoIterator<Item = Variable>,
{
Bool::Container(cvar, mvars.into_iter().collect())
}
pub fn variable(variable: Variable) -> Self {
Bool(Variable(variable), SendSet::default())
pub fn variable(var: Variable) -> Self {
Bool::Container(var, SendSet::default())
}
pub fn with_free(variable: Variable, rest: Vec<Atom>) -> Self {
let atom_set: SendSet<Atom> = rest.into_iter().collect();
Bool(Variable(variable), atom_set)
pub fn is_fully_simplified(&self, subs: &Subs) -> bool {
match self {
Shared => true,
Container(cvar, mvars) => {
!var_is_shared(subs, *cvar)
&& !(mvars.iter().any(|mvar| var_is_shared(subs, *mvar)))
}
}
}
pub fn from_parts(free: Atom, rest: Vec<Atom>) -> Self {
let atom_set: SendSet<Atom> = rest.into_iter().collect();
Bool(free, atom_set)
pub fn is_unique(&self, subs: &Subs) -> bool {
match self.simplify(subs) {
Shared => false,
_ => true,
}
}
pub fn variables(&self) -> SendSet<Variable> {
let mut result = SendSet::default();
match self {
Shared => SendSet::default(),
Container(cvar, mvars) => {
let mut mvars = mvars.clone();
mvars.insert(*cvar);
if let Variable(v) = self.0 {
result.insert(v);
}
for atom in &self.1 {
if let Variable(v) = atom {
result.insert(*v);
}
}
result
}
pub fn apply_subs(&mut self, subs: &mut Subs) {
self.0.apply_subs(subs);
for atom in self.1.iter_mut() {
atom.apply_subs(subs);
}
}
pub fn simplify(&self, subs: &Subs) -> Result<Vec<Variable>, Atom> {
match self.0 {
Atom::Zero => Err(Atom::Zero),
Atom::One => Err(Atom::One),
Atom::Variable(var) => {
// The var may still point to Zero or One!
match subs.get_without_compacting(var).content {
Content::Structure(FlatType::Boolean(nested)) => nested.simplify(subs),
_ => {
let mut result = Vec::new();
result.push(var);
for atom in &self.1 {
match atom {
Atom::Zero => {}
Atom::One => return Err(Atom::One),
Atom::Variable(v) => {
match subs.get_without_compacting(*v).content {
Content::Structure(FlatType::Boolean(nested)) => {
match nested.simplify(subs) {
Ok(variables) => {
for var in variables {
result.push(var);
}
}
Err(Atom::Zero) => {}
Err(Atom::One) => return Err(Atom::One),
Err(Atom::Variable(_)) => {
panic!("TODO nested variable")
}
}
}
_ => {
result.push(*v);
}
}
}
}
}
Ok(result)
}
}
mvars
}
}
}
@ -140,28 +185,33 @@ impl Bool {
where
F: FnMut(Variable) -> Variable,
{
let mut new_bound = SendSet::default();
match self {
Bool::Shared => Bool::Shared,
Bool::Container(cvar, mvars) => {
let new_cvar = f(*cvar);
let new_mvars = mvars.iter().map(|var| f(*var)).collect();
let new_free = if let Variable(v) = self.0 {
Variable(f(v))
} else {
self.0
};
for atom in &self.1 {
if let Variable(v) = atom {
new_bound.insert(Variable(f(*v)));
} else {
new_bound.insert(*atom);
Bool::Container(new_cvar, new_mvars)
}
}
Bool(new_free, new_bound)
}
pub fn is_unique(&self, subs: &Subs) -> bool {
match self.simplify(subs) {
Ok(_variables) => true,
Err(atom) => atom.is_unique(subs),
pub fn simplify(&self, subs: &Subs) -> Self {
match self {
Bool::Container(cvar, mvars) => {
let flattened_mvars = var_to_variables(subs, *cvar, mvars);
// find the parent variable, to remove distinct variables that all have the same
// parent. This prevents the signature from rendering as e.g. `( a | b | b)` and
// instead makes it `( a | b )`.
let parent_mvars = flattened_mvars
.into_iter()
.map(|v| subs.get_root_key_without_compacting(v))
.collect();
Bool::Container(*cvar, parent_mvars)
}
Bool::Shared => Bool::Shared,
}
}
}

View file

@ -1,7 +1,7 @@
use crate::boolean_algebra::{Atom, Bool};
use crate::boolean_algebra::Bool;
use crate::subs::{Content, FlatType, Subs, Variable};
use crate::types::name_type_var;
use roc_collections::all::{ImSet, MutMap, MutSet};
use roc_collections::all::{MutMap, MutSet};
use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::{Interns, ModuleId, Symbol};
@ -77,28 +77,34 @@ fn find_names_needed(
use crate::subs::Content::*;
use crate::subs::FlatType::*;
while let Some((recursive, _)) = subs.occurs(variable) {
if let Content::Structure(FlatType::TagUnion(tags, ext_var)) =
subs.get_without_compacting(recursive).content
{
let rec_var = subs.fresh_unnamed_flex_var();
while let Some((recursive, _chain)) = subs.occurs(variable) {
let content = subs.get_without_compacting(recursive).content;
match content {
Content::Structure(FlatType::TagUnion(tags, ext_var)) => {
let rec_var = subs.fresh_unnamed_flex_var();
let mut new_tags = MutMap::default();
let mut new_tags = MutMap::default();
for (label, args) in tags {
let new_args = args
.clone()
.into_iter()
.map(|var| if var == recursive { rec_var } else { var })
.collect();
for (label, args) in tags {
let new_args = args
.clone()
.into_iter()
.map(|var| if var == recursive { rec_var } else { var })
.collect();
new_tags.insert(label.clone(), new_args);
new_tags.insert(label.clone(), new_args);
}
let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, ext_var);
subs.set_content(recursive, Content::Structure(flat_type));
}
let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, ext_var);
subs.set_content(recursive, Content::Structure(flat_type));
} else {
panic!("unfixable recursive type in roc_types::pretty_print")
Content::Structure(FlatType::Boolean(Bool::Container(_cvar, _mvars))) => {
crate::boolean_algebra::flatten(subs, recursive);
}
_ => panic!(
"unfixable recursive type in roc_types::pretty_print {:?} {:?} {:?}",
recursive, variable, content
),
}
}
@ -168,32 +174,25 @@ fn find_names_needed(
find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
find_names_needed(rec_var, subs, roots, root_appearances, names_taken);
}
Structure(Boolean(b)) =>
// NOTE it's important that we traverse the variables in the same order as they are
// below in write_boolean, hence the call to `simplify`.
{
match b.simplify(subs) {
Err(Atom::Variable(var)) => {
Structure(Boolean(b)) => match b {
Bool::Shared => {}
Bool::Container(cvar, mvars) => {
find_names_needed(cvar, subs, roots, root_appearances, names_taken);
for var in mvars {
find_names_needed(var, subs, roots, root_appearances, names_taken);
}
Err(_) => {}
Ok(mut variables) => {
variables.sort();
for var in variables {
find_names_needed(var, subs, roots, root_appearances, names_taken);
}
}
}
}
},
Alias(symbol, args, _actual) => {
if let Symbol::ATTR_ATTR = symbol {
find_names_needed(args[0].1, subs, roots, root_appearances, names_taken);
find_names_needed(args[1].1, subs, roots, root_appearances, names_taken);
} else {
// TODO should we also look in the actual variable?
for (_, var) in args {
find_names_needed(var, subs, roots, root_appearances, names_taken);
}
// TODO should we also look in the actual variable?
// find_names_needed(_actual, subs, roots, root_appearances, names_taken);
}
}
Error | Structure(Erroneous(_)) | Structure(EmptyRecord) | Structure(EmptyTagUnion) => {
@ -212,6 +211,8 @@ pub fn name_all_type_vars(variable: Variable, subs: &mut Subs) {
find_names_needed(variable, subs, &mut roots, &mut appearances, &mut taken);
for root in roots {
// show the type variable number instead of `*`. useful for debugging
// set_root_name(root, (format!("<{:?}>", root).into()), subs);
if let Some(Appearances::Multiple) = appearances.get(&root) {
letters_used = name_root(letters_used, root, subs, &mut taken);
}
@ -226,21 +227,19 @@ fn name_root(
) -> u32 {
let (generated_name, new_letters_used) = name_type_var(letters_used, taken);
set_root_name(root, &generated_name, subs);
set_root_name(root, generated_name, subs);
new_letters_used
}
fn set_root_name(root: Variable, name: &Lowercase, subs: &mut Subs) {
fn set_root_name(root: Variable, name: Lowercase, subs: &mut Subs) {
use crate::subs::Content::*;
let mut descriptor = subs.get_without_compacting(root);
match descriptor.content {
FlexVar(None) => {
descriptor.content = FlexVar(Some(name.clone()));
// TODO is this necessary, or was mutating descriptor in place sufficient?
descriptor.content = FlexVar(Some(name));
subs.set(root, descriptor);
}
FlexVar(Some(_existing)) => {
@ -332,6 +331,14 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
Parens::InTypeParam,
);
}
// useful for debugging
if false {
buf.push_str("[[ but really ");
let content = subs.get_without_compacting(_actual).content;
write_content(env, content, subs, buf, parens);
buf.push_str("]]");
}
}),
}
}
@ -585,13 +592,33 @@ pub fn chase_ext_record(
}
fn write_boolean(env: &Env, boolean: Bool, subs: &Subs, buf: &mut String, parens: Parens) {
match boolean.simplify(subs) {
Err(atom) => write_boolean_atom(env, atom, subs, buf, parens),
Ok(mut variables) => {
variables.sort();
let mut buffers_set = ImSet::default();
use crate::boolean_algebra::var_is_shared;
match boolean.simplify(subs) {
Bool::Shared => {
buf.push_str("Shared");
}
Bool::Container(cvar, mvars) if mvars.iter().all(|v| var_is_shared(subs, *v)) => {
debug_assert!(!var_is_shared(subs, cvar));
write_content(
env,
subs.get_without_compacting(cvar).content,
subs,
buf,
Parens::Unnecessary,
);
}
Bool::Container(cvar, mvars) => {
debug_assert!(!var_is_shared(subs, cvar));
let mut buffers = Vec::with_capacity(mvars.len());
for v in mvars {
// don't print shared in a container
if var_is_shared(subs, v) {
continue;
}
for v in variables {
let mut inner_buf: String = "".to_string();
write_content(
env,
@ -600,41 +627,25 @@ fn write_boolean(env: &Env, boolean: Bool, subs: &Subs, buf: &mut String, parens
&mut inner_buf,
parens,
);
buffers_set.insert(inner_buf);
buffers.push(inner_buf);
}
let mut buffers: Vec<String> = buffers_set.into_iter().collect();
// sort type variables alphabetically
buffers.sort();
let combined = buffers.join(" | ");
let write_parens = buffers.len() > 1;
if write_parens {
buf.push_str("(");
}
buf.push_str("(");
write_content(
env,
subs.get_without_compacting(cvar).content,
subs,
buf,
Parens::Unnecessary,
);
buf.push_str(" | ");
buf.push_str(&combined);
if write_parens {
buf.push_str(")");
}
}
}
}
fn write_boolean_atom(env: &Env, atom: Atom, subs: &Subs, buf: &mut String, parens: Parens) {
match atom {
Atom::Variable(var) => write_content(
env,
subs.get_without_compacting(var).content,
subs,
buf,
parens,
),
Atom::Zero => {
buf.push_str("Shared");
}
Atom::One => {
buf.push_str("Unique");
buf.push_str(")");
}
}
}
@ -701,7 +712,7 @@ fn write_apply(
_ => default_case(subs, arg_content),
},
_ => default_case(subs, arg_content),
_other => default_case(subs, arg_content),
},
_ => default_case(subs, arg_content),
},

View file

@ -49,28 +49,38 @@ pub enum SolvedType {
Alias(Symbol, Vec<(Lowercase, SolvedType)>, Box<SolvedType>),
/// a boolean algebra Bool
Boolean(SolvedAtom, Vec<SolvedAtom>),
Boolean(SolvedBool),
/// A type error
Error,
}
#[derive(Debug, Clone, PartialEq)]
pub enum SolvedAtom {
Zero,
One,
Variable(VarId),
pub enum SolvedBool {
SolvedShared,
SolvedContainer(VarId, Vec<VarId>),
}
impl SolvedAtom {
pub fn from_atom(atom: boolean_algebra::Atom) -> Self {
use boolean_algebra::Atom::*;
impl SolvedBool {
pub fn from_bool(boolean: &boolean_algebra::Bool, subs: &Subs) -> Self {
use boolean_algebra::Bool;
// NOTE we blindly trust that `var` is a root and has a FlexVar as content
match atom {
Zero => SolvedAtom::Zero,
One => SolvedAtom::One,
Variable(var) => SolvedAtom::Variable(VarId::from_var(var)),
match boolean {
Bool::Shared => SolvedBool::SolvedShared,
Bool::Container(cvar, mvars) => {
debug_assert!(matches!(
subs.get_without_compacting(*cvar).content,
crate::subs::Content::FlexVar(_)
));
SolvedBool::SolvedContainer(
VarId::from_var(*cvar, subs),
mvars
.iter()
.map(|mvar| VarId::from_var(*mvar, subs))
.collect(),
)
}
}
}
}
@ -154,7 +164,7 @@ impl SolvedType {
}
SolvedType::RecursiveTagUnion(
VarId::from_var(rec_var),
VarId::from_var(rec_var, solved_subs.inner()),
solved_tags,
Box::new(solved_ext),
)
@ -170,15 +180,7 @@ impl SolvedType {
SolvedType::Alias(symbol, solved_args, Box::new(solved_type))
}
Boolean(val) => {
let free = SolvedAtom::from_atom(val.0);
let mut rest = Vec::with_capacity(val.1.len());
for atom in val.1 {
rest.push(SolvedAtom::from_atom(atom));
}
SolvedType::Boolean(free, rest)
}
Boolean(val) => SolvedType::Boolean(SolvedBool::from_bool(&val, solved_subs.inner())),
Variable(var) => Self::from_var(solved_subs.inner(), var),
}
}
@ -187,7 +189,7 @@ impl SolvedType {
use crate::subs::Content::*;
match subs.get_without_compacting(var).content {
FlexVar(_) => SolvedType::Flex(VarId::from_var(var)),
FlexVar(_) => SolvedType::Flex(VarId::from_var(var, subs)),
RigidVar(name) => SolvedType::Rigid(name),
Structure(flat_type) => Self::from_flat_type(subs, flat_type),
Alias(symbol, args, actual_var) => {
@ -277,19 +279,15 @@ impl SolvedType {
let ext = Self::from_var(subs, ext_var);
SolvedType::RecursiveTagUnion(VarId::from_var(rec_var), new_tags, Box::new(ext))
SolvedType::RecursiveTagUnion(
VarId::from_var(rec_var, subs),
new_tags,
Box::new(ext),
)
}
EmptyRecord => SolvedType::EmptyRecord,
EmptyTagUnion => SolvedType::EmptyTagUnion,
Boolean(val) => {
let free = SolvedAtom::from_atom(val.0);
let mut rest = Vec::with_capacity(val.1.len());
for atom in val.1 {
rest.push(SolvedAtom::from_atom(atom));
}
SolvedType::Boolean(free, rest)
}
Boolean(val) => SolvedType::Boolean(SolvedBool::from_bool(&val, subs)),
Erroneous(problem) => SolvedType::Erroneous(problem),
}
}

View file

@ -199,7 +199,8 @@ impl UnifyKey for Variable {
pub struct VarId(u32);
impl VarId {
pub fn from_var(var: Variable) -> Self {
pub fn from_var(var: Variable, subs: &Subs) -> Self {
let var = subs.get_root_key_without_compacting(var);
let Variable(n) = var;
VarId(n)
@ -876,13 +877,26 @@ where
}
fn var_to_err_type(subs: &mut Subs, state: &mut ErrorTypeState, var: Variable) -> ErrorType {
let desc = subs.get(var);
let mut desc = subs.get(var);
if desc.mark == Mark::OCCURS {
ErrorType::Infinite
} else {
subs.set_mark(var, Mark::OCCURS);
if false {
// useful for debugging
match desc.content {
Content::FlexVar(_) => {
desc.content = Content::FlexVar(Some(format!("{:?}", var).into()));
}
Content::RigidVar(_) => {
desc.content = Content::RigidVar(format!("{:?}", var).into());
}
_ => {}
}
}
let err_type = content_to_err_type(subs, state, var, desc.content);
subs.set_mark(var, desc.mark);

View file

@ -130,20 +130,17 @@ impl fmt::Debug for Type {
write!(f, " ")?;
}
let mut any_written_yet = false;
for (label, arguments) in tags {
if any_written_yet {
write!(f, ", ")?;
} else {
any_written_yet = true;
}
let mut it = tags.iter().peekable();
while let Some((label, arguments)) = it.next() {
write!(f, "{:?}", label)?;
for argument in arguments {
write!(f, " {:?}", argument)?;
}
if it.peek().is_some() {
write!(f, ", ")?;
}
}
if !tags.is_empty() {
@ -174,20 +171,17 @@ impl fmt::Debug for Type {
write!(f, " ")?;
}
let mut any_written_yet = false;
for (label, arguments) in tags {
if any_written_yet {
write!(f, ", ")?;
} else {
any_written_yet = true;
}
let mut it = tags.iter().peekable();
while let Some((label, arguments)) = it.next() {
write!(f, "{:?}", label)?;
for argument in arguments {
write!(f, " {:?}", argument)?;
}
if it.peek().is_some() {
write!(f, ", ")?;
}
}
if !tags.is_empty() {
@ -378,6 +372,36 @@ impl Type {
}
}
pub fn contains_variable(&self, rep_variable: Variable) -> bool {
use Type::*;
match self {
Variable(v) => *v == rep_variable,
Function(args, ret) => {
ret.contains_variable(rep_variable)
|| args.iter().any(|arg| arg.contains_variable(rep_variable))
}
RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => {
ext.contains_variable(rep_variable)
|| tags
.iter()
.map(|v| v.1.iter())
.flatten()
.any(|arg| arg.contains_variable(rep_variable))
}
Record(fields, ext) => {
ext.contains_variable(rep_variable)
|| fields
.values()
.any(|arg| arg.contains_variable(rep_variable))
}
Alias(_, _, actual_type) => actual_type.contains_variable(rep_variable),
Apply(_, args) => args.iter().any(|arg| arg.contains_variable(rep_variable)),
EmptyRec | EmptyTagUnion | Erroneous(_) | Boolean(_) => false,
}
}
pub fn symbols(&self) -> ImSet<Symbol> {
let mut found_symbols = ImSet::default();
symbols_help(self, &mut found_symbols);
@ -431,6 +455,36 @@ impl Type {
actual_type.instantiate_aliases(region, aliases, var_store, introduced);
}
Apply(Symbol::ATTR_ATTR, attr_args) => {
use boolean_algebra::Bool;
debug_assert_eq!(attr_args.len(), 2);
let mut it = attr_args.iter_mut();
let uniqueness_type = it.next().unwrap();
let base_type = it.next().unwrap();
// instantiate the rest
base_type.instantiate_aliases(region, aliases, var_store, introduced);
// correct uniqueness type
// if this attr contains an alias of a recursive tag union, then the uniqueness
// attribute on the recursion variable must match the uniqueness of the whole tag
// union. We enforce that here.
if let Some(rec_uvar) = find_rec_var_uniqueness(base_type, aliases) {
if let Bool::Container(unbound_cvar, mvars1) = rec_uvar {
if let Type::Boolean(Bool::Container(bound_cvar, mvars2)) = uniqueness_type
{
debug_assert!(mvars1.is_empty());
debug_assert!(mvars2.is_empty());
let mut substitution = ImMap::default();
substitution.insert(unbound_cvar, Type::Variable(*bound_cvar));
base_type.substitute(&substitution);
}
}
}
}
Apply(symbol, args) => {
if let Some(alias) = aliases.get(symbol) {
if args.len() != alias.vars.len() {
@ -463,9 +517,44 @@ impl Type {
substitution.insert(*placeholder, filler);
}
// instantiate "hidden" uniqueness variables
use boolean_algebra::Bool;
// Instantiate "hidden" uniqueness variables
//
// Aliases can hide uniqueness variables: e.g. in
//
// Model : { x : Int, y : Bool }
//
// Its lifted variant is
//
// Attr a Model
//
// where the `a` doesn't really mention the attributes on the fields.
for variable in actual.variables() {
if !substitution.contains_key(&variable) {
// Leave attributes on recursion variables untouched!
//
// In a recursive type like
//
// > [ Z, S Peano ] as Peano
//
// By default the lifted version is
//
// > Attr a ([ Z, S (Attr b Peano) ] as Peano)
//
// But, it must be true that a = b because Peano is self-recursive.
// Therefore we earlier have substituted
//
// > Attr a ([ Z, S (Attr a Peano) ] as Peano)
//
// And now we must make sure the `a`s stay the same variable, i.e.
// don't re-instantiate it here.
if let Some(Bool::Container(unbound_cvar, _)) = alias.uniqueness {
if variable == unbound_cvar {
introduced.insert(variable);
continue;
}
}
let var = var_store.fresh();
substitution.insert(variable, Type::Variable(var));
@ -601,6 +690,34 @@ fn variables_help(tipe: &Type, accum: &mut ImSet<Variable>) {
}
}
/// We're looking for an alias whose actual type is a recursive tag union
/// if `base_type` is one, return the uniqueness variable of the alias.
fn find_rec_var_uniqueness(
base_type: &Type,
aliases: &ImMap<Symbol, Alias>,
) -> Option<boolean_algebra::Bool> {
use Type::*;
if let Alias(symbol, _, actual) = base_type {
match **actual {
Alias(_, _, _) => find_rec_var_uniqueness(actual, aliases),
RecursiveTagUnion(_, _, _) => {
if let Some(alias) = aliases.get(symbol) {
// alias with a recursive tag union must have its uniqueness set
debug_assert!(alias.uniqueness.is_some());
alias.uniqueness.clone()
} else {
unreachable!("aliases must be defined in the set of aliases!")
}
}
_ => None,
}
} else {
None
}
}
pub struct RecordStructure {
pub fields: MutMap<Lowercase, Variable>,
pub ext: Variable,
@ -696,6 +813,7 @@ pub enum PatternCategory {
pub struct Alias {
pub region: Region,
pub vars: Vec<Located<(Lowercase, Variable)>>,
pub uniqueness: Option<boolean_algebra::Bool>,
pub typ: Type,
}
@ -725,7 +843,7 @@ pub enum Mismatch {
CanonicalizationProblem,
}
#[derive(PartialEq, Eq, Debug, Clone)]
#[derive(PartialEq, Eq, Clone)]
pub enum ErrorType {
Infinite,
Type(Symbol, Vec<ErrorType>),
@ -740,6 +858,13 @@ pub enum ErrorType {
Error,
}
impl std::fmt::Debug for ErrorType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO remove clone
write!(f, "{:?}", write_debug_error_type(self.clone()))
}
}
impl ErrorType {
pub fn unwrap_alias(self) -> ErrorType {
match self {
@ -856,6 +981,179 @@ fn write_error_type_help(
}
}
pub fn write_debug_error_type(error_type: ErrorType) -> String {
let mut buf = String::new();
write_debug_error_type_help(error_type, &mut buf, Parens::Unnecessary);
buf
}
fn write_debug_error_type_help(error_type: ErrorType, buf: &mut String, parens: Parens) {
use ErrorType::*;
match error_type {
Infinite => buf.push_str(""),
Error => buf.push_str("?"),
FlexVar(name) => buf.push_str(name.as_str()),
RigidVar(name) => buf.push_str(name.as_str()),
Type(symbol, arguments) => {
let write_parens = parens == Parens::InTypeParam && !arguments.is_empty();
if write_parens {
buf.push('(');
}
buf.push_str(&format!("{:?}", symbol));
for arg in arguments {
buf.push(' ');
write_debug_error_type_help(arg, buf, Parens::InTypeParam);
}
if write_parens {
buf.push(')');
}
}
Alias(Symbol::NUM_NUM, mut arguments, _actual) => {
debug_assert!(arguments.len() == 1);
let argument = arguments.remove(0).1;
match argument {
Type(Symbol::INT_INTEGER, _) => {
buf.push_str("Int");
}
Type(Symbol::FLOAT_FLOATINGPOINT, _) => {
buf.push_str("Float");
}
other => {
let write_parens = parens == Parens::InTypeParam;
if write_parens {
buf.push('(');
}
buf.push_str("Num ");
write_debug_error_type_help(other, buf, Parens::InTypeParam);
if write_parens {
buf.push(')');
}
}
}
}
Alias(symbol, arguments, _actual) => {
let write_parens = parens == Parens::InTypeParam && !arguments.is_empty();
if write_parens {
buf.push('(');
}
buf.push_str(&format!("{:?}", symbol));
for arg in arguments {
buf.push(' ');
write_debug_error_type_help(arg.1, buf, Parens::InTypeParam);
}
// useful for debugging
let write_out_alias = true;
if write_out_alias {
buf.push_str("[[ but really ");
write_debug_error_type_help(*_actual, buf, Parens::Unnecessary);
buf.push_str("]]");
}
if write_parens {
buf.push(')');
}
}
Function(arguments, result) => {
let write_parens = parens != Parens::Unnecessary;
if write_parens {
buf.push('(');
}
let mut it = arguments.into_iter().peekable();
while let Some(arg) = it.next() {
write_debug_error_type_help(arg, buf, Parens::InFn);
if it.peek().is_some() {
buf.push_str(", ");
}
}
buf.push_str(" -> ");
write_debug_error_type_help(*result, buf, Parens::InFn);
if write_parens {
buf.push(')');
}
}
Record(fields, ext) => {
buf.push('{');
for (label, content) in fields {
buf.push_str(label.as_str());
buf.push_str(": ");
write_debug_error_type_help(content, buf, Parens::Unnecessary);
}
buf.push('}');
write_type_ext(ext, buf);
}
TagUnion(tags, ext) => {
buf.push('[');
let mut it = tags.into_iter().peekable();
while let Some((tag, args)) = it.next() {
buf.push_str(&format!("{:?}", tag));
for arg in args {
buf.push_str(" ");
write_debug_error_type_help(arg, buf, Parens::InTypeParam);
}
if it.peek().is_some() {
buf.push_str(", ");
}
}
buf.push(']');
write_type_ext(ext, buf);
}
RecursiveTagUnion(rec, tags, ext) => {
buf.push('[');
let mut it = tags.into_iter().peekable();
while let Some((tag, args)) = it.next() {
buf.push_str(&format!("{:?}", tag));
for arg in args {
buf.push_str(" ");
write_debug_error_type_help(arg, buf, Parens::Unnecessary);
}
if it.peek().is_some() {
buf.push_str(", ");
}
}
buf.push(']');
write_type_ext(ext, buf);
buf.push_str(" as ");
write_debug_error_type_help(*rec, buf, Parens::Unnecessary);
}
Boolean(boolean_algebra::Bool::Shared) => buf.push_str("Shared"),
Boolean(boolean_algebra::Bool::Container(mvar, cvars)) => {
buf.push_str(&format!("Container({:?}, {:?})", mvar, cvars))
}
}
}
#[derive(PartialEq, Eq, Debug, Clone)]
pub enum TypeExt {
Closed,

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.
@ -510,6 +515,17 @@ fn unify_tag_union(
}
}
/// Is the given variable a structure. Does not consider Attr itself a structure, and instead looks
/// into it.
fn is_structure(var: Variable, subs: &mut Subs) -> bool {
match subs.get(var).content {
Content::Alias(_, _, actual) => is_structure(actual, subs),
Content::Structure(FlatType::Apply(Symbol::ATTR_ATTR, args)) => is_structure(args[1], subs),
Content::Structure(_) => true,
_ => false,
}
}
fn unify_shared_tags(
subs: &mut Subs,
pool: &mut Pool,
@ -529,7 +545,52 @@ 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.
//
// 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.
let problems = if let Some(rvar) = recursion_var {
if expected == rvar {
unify_pool(subs, pool, actual, ctx.second)
} else if is_structure(actual, subs) {
// the recursion variable is hidden behind some structure (commonly an Attr
// with uniqueness inference). Thus we must expand the recursive tag union to
// unify if with the non-recursive one. Thus:
// replace the rvar with ctx.second (the whole recursive tag union) in expected
subs.explicit_substitute(rvar, ctx.second, expected);
// but, by the `is_structure` condition above, only if we're unifying with a structure!
// when `actual` is just a flex/rigid variable, the substitution would expand a
// recursive tag union infinitely!
unify_pool(subs, pool, actual, expected)
} else {
// unification with a non-structure is trivial
unify_pool(subs, pool, actual, expected)
}
} else {
// we always unify NonRecursive with Recursive, so this should never happen
debug_assert_ne!(Some(actual), recursion_var);
unify_pool(subs, pool, actual, expected)
};
if problems.is_empty() {
matching_vars.push(actual);
@ -611,6 +672,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 +697,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 +793,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 +830,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 +840,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 +851,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,