Merge trunk

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

View file

@ -24,9 +24,9 @@ interface Num
## a more specific type based on how they're used. ## 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, ## 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 ## but because `List.len` returns a `Len`, the `1` ends up changing from
## `Num *` to the more specific `Ulen`, and the expression as a whole ## `Num *` to the more specific `Len`, and the expression as a whole
## ends up having the type `Ulen`. ## ends up having the type `Len`.
## ##
## Sometimes number literals don't become more specific. For example, ## Sometimes number literals don't become more specific. For example,
## the #Num.toStr function has the type `Num * -> Str`. This means that ## 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 ## * `215u8` is a `215` value of type #U8
## * `76.4f32` is a `76.4` value of type #F32 ## * `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 ## In practice, these are rarely needed. It's most common to write
## number literals without any suffix. ## number literals without any suffix.
@ -116,7 +116,7 @@ U64 : Int @U64
I128 : Int @I128 I128 : Int @I128
U128 : Int @U128 U128 : Int @U128
Ilen : Int @Ilen 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. ## 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 | ## | ` (over 340 undecillion) 0` | #U128 | 16 Bytes |
## | ` 340_282_366_920_938_463_463_374_607_431_768_211_455` | | | ## | ` 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 ## Roc also has one variable-size integer type: #Len. The size of #Len is equal
## are determined by the [machine word length](https://en.wikipedia.org/wiki/Word_(computer_architecture)) ## to the size of a memory address, which varies by system. For example, when
## of the system you're compiling for. (The "len" in their names is short for "length of a machine word.") ## compiling for a 64-bit system, #Len is the same as #U64. When compiling for a
## For example, when compiling for a 64-bit target, #Ulen is the same as #U64, ## 32-bit system, it's the same as #U32.
## 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. ## 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 ## 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`), ## or too small to fit in that range (e.g. calling `Int.maxI32 + 1`),
@ -585,6 +588,21 @@ hash64 : a -> U64
## Limits ## 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 ## The highest number that can be stored in an #I32 without overflowing its
## available memory and crashing. ## available memory and crashing.
## ##
@ -599,6 +617,32 @@ maxI32 : I32
## #Int.maxI32, which means if you call #Num.abs on #Int.minI32, it will overflow and crash! ## #Int.maxI32, which means if you call #Num.abs on #Int.minI32, it will overflow and crash!
minI32 : I32 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. ## 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! ## If you go higher than this, your running Roc code will crash - so be careful not to!

View file

@ -512,7 +512,7 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
), ),
); );
// push : List a -> a -> List a // push : List elem -> elem -> List elem
add_type( add_type(
Symbol::LIST_PUSH, Symbol::LIST_PUSH,
SolvedType::Func( SolvedType::Func(
@ -527,6 +527,15 @@ pub fn types() -> MutMap<Symbol, (SolvedType, Region)> {
SolvedType::Func(vec![flex(TVAR1)], Box::new(list_type(flex(TVAR1)))), SolvedType::Func(vec![flex(TVAR1)], Box::new(list_type(flex(TVAR1)))),
); );
// repeat : Int, elem -> List elem
add_type(
Symbol::LIST_REPEAT,
SolvedType::Func(
vec![int_type(), flex(TVAR1)],
Box::new(list_type(flex(TVAR1))),
),
);
// len : List * -> Int // len : List * -> Int
add_type( add_type(
Symbol::LIST_LEN, Symbol::LIST_LEN,

File diff suppressed because it is too large Load diff

View file

@ -237,7 +237,8 @@ fn can_annotation_help(
let var_name = Lowercase::from(ident); let var_name = Lowercase::from(ident);
if let Some(var) = introduced_variables.var_by_name(&var_name) { 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 { } else {
let var = var_store.fresh(); let var = var_store.fresh();
@ -276,11 +277,15 @@ fn can_annotation_help(
let alias = Alias { let alias = Alias {
region, region,
vars: lowercase_vars, vars: lowercase_vars,
typ: alias_actual.clone(), uniqueness: None,
typ: alias_actual,
}; };
local_aliases.insert(symbol, alias); 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. // This is a syntactically invalid type alias.

View file

@ -245,6 +245,7 @@ pub fn canonicalize_defs<'a>(
let alias = roc_types::types::Alias { let alias = roc_types::types::Alias {
region: ann.region, region: ann.region,
vars: can_vars, vars: can_vars,
uniqueness: None,
typ: can_ann.typ, typ: can_ann.typ,
}; };
aliases.insert(symbol, alias); aliases.insert(symbol, alias);

View file

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

View file

@ -146,6 +146,14 @@ impl Scope {
vars: Vec<Located<(Lowercase, Variable)>>, vars: Vec<Located<(Lowercase, Variable)>>,
typ: Type, 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::ident::Lowercase;
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region}; use roc_region::all::{Located, Region};
use roc_types::boolean_algebra::{Atom, Bool}; use roc_types::boolean_algebra::Bool;
use roc_types::solved_types::{BuiltinAlias, SolvedAtom, SolvedType}; use roc_types::solved_types::{BuiltinAlias, SolvedBool, SolvedType};
use roc_types::subs::{VarId, VarStore, Variable}; use roc_types::subs::{VarId, VarStore, Variable};
use roc_types::types::{Alias, Problem, Type}; use roc_types::types::{Alias, Problem, Type};
@ -150,6 +150,7 @@ where
let alias = Alias { let alias = Alias {
vars, vars,
region: builtin_alias.region, region: builtin_alias.region,
uniqueness: None,
typ: actual, typ: actual,
}; };
@ -271,15 +272,15 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut V
Box::new(to_type(ext, free_vars, var_store)), Box::new(to_type(ext, free_vars, var_store)),
) )
} }
Boolean(solved_free, solved_rest) => { Boolean(SolvedBool::SolvedShared) => Type::Boolean(Bool::Shared),
let free = to_atom(solved_free, free_vars, var_store); Boolean(SolvedBool::SolvedContainer(solved_cvar, solved_mvars)) => {
let mut rest = Vec::with_capacity(solved_rest.len()); let cvar = var_id_to_var(*solved_cvar, free_vars, var_store);
for solved_atom in solved_rest { let mvars = solved_mvars
rest.push(to_atom(solved_atom, free_vars, var_store)); .iter()
} .map(|var_id| var_id_to_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) => { Alias(symbol, solved_type_variables, solved_actual) => {
let mut type_variables = Vec::with_capacity(solved_type_variables.len()); let mut type_variables = Vec::with_capacity(solved_type_variables.len());
@ -297,24 +298,14 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &mut V
} }
} }
pub fn to_atom( fn var_id_to_var(var_id: VarId, free_vars: &mut FreeVars, var_store: &mut VarStore) -> Variable {
solved_atom: &SolvedAtom, if let Some(var) = free_vars.unnamed_vars.get(&var_id) {
free_vars: &mut FreeVars, *var
var_store: &mut VarStore, } else {
) -> Atom { let var = var_store.fresh();
match solved_atom { free_vars.unnamed_vars.insert(var_id, var);
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);
Atom::Variable(var) var
}
}
} }
} }
@ -347,6 +338,7 @@ pub fn constrain_imported_aliases(
let alias = Alias { let alias = Alias {
vars, vars,
region: imported_alias.region, region: imported_alias.region,
uniqueness: imported_alias.uniqueness,
typ: actual, 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::ident::{Ident, Lowercase};
use roc_module::symbol::{ModuleId, Symbol}; use roc_module::symbol::{ModuleId, Symbol};
use roc_region::all::{Located, Region}; 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::subs::{VarStore, Variable};
use roc_types::types::AnnotationSource::{self, *}; use roc_types::types::AnnotationSource::{self, *};
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
@ -248,10 +248,7 @@ fn constrain_pattern(
let empty_var = var_store.fresh(); let empty_var = var_store.fresh();
state.vars.push(empty_var); state.vars.push(empty_var);
state.vars.extend(pattern_uniq_vars.clone()); state.vars.extend(pattern_uniq_vars.clone());
Bool::with_free( Bool::container(empty_var, pattern_uniq_vars)
empty_var,
pattern_uniq_vars.into_iter().map(Atom::Variable).collect(),
)
}; };
let record_type = attr_type( let record_type = attr_type(
@ -305,10 +302,7 @@ fn constrain_pattern(
let empty_var = var_store.fresh(); let empty_var = var_store.fresh();
state.vars.push(empty_var); state.vars.push(empty_var);
state.vars.extend(pattern_uniq_vars.clone()); state.vars.extend(pattern_uniq_vars.clone());
Bool::with_free( Bool::container(empty_var, pattern_uniq_vars)
empty_var,
pattern_uniq_vars.into_iter().map(Atom::Variable).collect(),
)
}; };
let union_type = attr_type( let union_type = attr_type(
tag_union_uniq_type, tag_union_uniq_type,
@ -1300,8 +1294,7 @@ pub fn constrain_expr(
field_types.insert(field.clone(), field_type.clone()); field_types.insert(field.clone(), field_type.clone());
let record_uniq_var = var_store.fresh(); let record_uniq_var = var_store.fresh();
let record_uniq_type = let record_uniq_type = Bool::container(record_uniq_var, vec![field_uniq_var]);
Bool::with_free(record_uniq_var, vec![Atom::Variable(field_uniq_var)]);
let record_type = attr_type( let record_type = attr_type(
record_uniq_type, record_uniq_type,
Type::Record(field_types, Box::new(Type::Variable(*ext_var))), Type::Record(field_types, Box::new(Type::Variable(*ext_var))),
@ -1358,8 +1351,7 @@ pub fn constrain_expr(
field_types.insert(field.clone(), field_type.clone()); field_types.insert(field.clone(), field_type.clone());
let record_uniq_var = var_store.fresh(); let record_uniq_var = var_store.fresh();
let record_uniq_type = let record_uniq_type = Bool::container(record_uniq_var, vec![field_uniq_var]);
Bool::with_free(record_uniq_var, vec![Atom::Variable(field_uniq_var)]);
let record_type = attr_type( let record_type = attr_type(
record_uniq_type, record_uniq_type,
Type::Record(field_types, Box::new(Type::Variable(*ext_var))), Type::Record(field_types, Box::new(Type::Variable(*ext_var))),
@ -1444,10 +1436,10 @@ fn constrain_var(
applied_usage_constraint.insert(symbol_for_lookup); applied_usage_constraint.insert(symbol_for_lookup);
let mut variables = Vec::new(); 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); 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! // NOTE breaking the expectation up like this REALLY matters!
let new_expected = Expected::NoExpectation(record_type.clone()); let new_expected = Expected::NoExpectation(record_type.clone());
@ -1468,128 +1460,176 @@ fn constrain_by_usage(
usage: &Usage, usage: &Usage,
var_store: &mut VarStore, var_store: &mut VarStore,
introduced: &mut Vec<Variable>, introduced: &mut Vec<Variable>,
) -> (Atom, Vec<Atom>, Type) { ) -> (Bool, Type) {
use Mark::*;
use Usage::*; use Usage::*;
match usage { match usage {
Simple(Shared) => { Simple(Mark::Shared) => {
let var = var_store.fresh(); let var = var_store.fresh();
introduced.push(var); 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 var = var_store.fresh();
let uvar = var_store.fresh(); let uvar = var_store.fresh();
introduced.push(var); introduced.push(var);
introduced.push(uvar); introduced.push(uvar);
(Atom::Variable(uvar), vec![], Type::Variable(var)) (Bool::container(uvar, vec![]), Type::Variable(var))
} }
Usage::Access(Container::Record, mark, fields) => { Usage::Access(Container::Record, mark, fields) => {
let (record_uniq_var, atoms, ext_type) = let (record_bool, ext_type) = constrain_by_usage(&Simple(*mark), var_store, introduced);
constrain_by_usage(&Simple(*mark), var_store, introduced);
debug_assert!(atoms.is_empty());
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) => { Usage::Update(Container::Record, _, fields) => {
let record_uvar = var_store.fresh(); let record_uvar = var_store.fresh();
let record_uniq_var = Atom::Variable(record_uvar);
introduced.push(record_uvar); introduced.push(record_uvar);
let record_bool = Bool::variable(record_uvar);
let ext_var = var_store.fresh(); let ext_var = var_store.fresh();
let ext_type = Type::Variable(ext_var); let ext_type = Type::Variable(ext_var);
introduced.push(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) => { Usage::Access(Container::List, mark, fields) => {
let (list_uniq_var, atoms, _ext_type) = let (list_bool, _ext_type) = constrain_by_usage(&Simple(*mark), var_store, introduced);
constrain_by_usage(&Simple(*mark), var_store, introduced);
debug_assert!(atoms.is_empty());
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) => { Usage::Update(Container::List, _, fields) => {
let list_uvar = var_store.fresh(); let list_uvar = var_store.fresh();
introduced.push(list_uvar); 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( fn constrain_by_usage_record(
fields: &FieldAccess, fields: &FieldAccess,
record_uniq_var: Atom, record_bool: Bool,
ext_type: Type, ext_type: Type,
introduced: &mut Vec<Variable>, introduced: &mut Vec<Variable>,
var_store: &mut VarStore, var_store: &mut VarStore,
) -> (Atom, Vec<Atom>, Type) { ) -> (Bool, Type) {
let mut field_types = SendMap::default(); let mut field_types = SendMap::default();
if fields.is_empty() { match record_bool {
( _ if fields.is_empty() => (record_bool, Type::Record(field_types, Box::new(ext_type))),
record_uniq_var, Bool::Shared => {
vec![], for (lowercase, nested_usage) in fields.clone().into_iter() {
Type::Record(field_types, Box::new(ext_type)), let (_, nested_type) = constrain_by_usage(&nested_usage, var_store, introduced);
)
} else {
let mut uniq_vars = Vec::with_capacity(fields.len());
for (lowercase, nested_usage) in fields.clone().into_iter() { let field_type = attr_type(Bool::Shared, nested_type);
let (uvar, atoms, nested_type) =
constrain_by_usage(&nested_usage, var_store, introduced);
for atom in &atoms { field_types.insert(lowercase.clone(), field_type);
uniq_vars.push(*atom);
} }
uniq_vars.push(uvar); (
Bool::Shared,
let field_type = attr_type(Bool::from_parts(uvar, atoms), nested_type); Type::Record(
field_types,
field_types.insert(lowercase.clone(), field_type); // 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),
),
)
} }
} }
@ -1702,7 +1742,7 @@ fn constrain_def_pattern(
fn annotation_to_attr_type( fn annotation_to_attr_type(
var_store: &mut VarStore, var_store: &mut VarStore,
ann: &Type, ann: &Type,
rigids: &mut ImMap<Variable, Variable>, rigids: &mut ImSet<Variable>,
change_var_kind: bool, change_var_kind: bool,
) -> (Vec<Variable>, Type) { ) -> (Vec<Variable>, Type) {
use roc_types::types::Type::*; use roc_types::types::Type::*;
@ -1710,19 +1750,12 @@ fn annotation_to_attr_type(
match ann { match ann {
Variable(var) => { Variable(var) => {
if change_var_kind { if change_var_kind {
if let Some(uvar) = rigids.get(var) { let uvar = var_store.fresh();
( rigids.insert(uvar);
vec![], (
attr_type(Bool::variable(*uvar), Type::Variable(*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)),
)
}
} else { } else {
(vec![], Type::Variable(*var)) (vec![], Type::Variable(*var))
} }
@ -1762,9 +1795,7 @@ fn annotation_to_attr_type(
// A rigid behind an attr has already been lifted, don't do it again! // A rigid behind an attr has already been lifted, don't do it again!
let (result_vars, result_lifted) = match args[1] { let (result_vars, result_lifted) = match args[1] {
Type::Variable(_) => match uniq_type { Type::Variable(_) => match uniq_type {
Type::Boolean(Bool(Atom::Variable(urigid), _)) => { Type::Boolean(Bool::Container(urigid, _)) => (vec![urigid], args[1].clone()),
(vec![urigid], args[1].clone())
}
_ => (vec![], args[1].clone()), _ => (vec![], args[1].clone()),
}, },
_ => annotation_to_attr_type(var_store, &args[1], rigids, change_var_kind), _ => annotation_to_attr_type(var_store, &args[1], rigids, change_var_kind),
@ -1833,26 +1864,46 @@ fn annotation_to_attr_type(
) )
} }
RecursiveTagUnion(rec_var, tags, ext_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 uniq_var = var_store.fresh();
let mut vars = Vec::with_capacity(tags.len()); let mut vars = Vec::with_capacity(tags.len());
let mut lifted_tags = 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 { 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); annotation_to_attr_type_many(var_store, fields, rigids, change_var_kind);
vars.extend(new_vars); vars.extend(new_vars);
for f in lifted_fields.iter_mut() {
f.substitute(&substitutions);
}
lifted_tags.push((tag.clone(), lifted_fields)); lifted_tags.push((tag.clone(), lifted_fields));
} }
vars.push(uniq_var); vars.push(uniq_var);
( let result = attr_type(
vars, Bool::variable(uniq_var),
attr_type( Type::RecursiveTagUnion(*rec_var, lifted_tags, ext_type.clone()),
Bool::variable(uniq_var), );
Type::RecursiveTagUnion(*rec_var, lifted_tags, ext_type.clone()),
), (vars, result)
)
} }
Alias(symbol, fields, actual) => { Alias(symbol, fields, actual) => {
@ -1891,7 +1942,7 @@ fn annotation_to_attr_type(
fn annotation_to_attr_type_many( fn annotation_to_attr_type_many(
var_store: &mut VarStore, var_store: &mut VarStore,
anns: &[Type], anns: &[Type],
rigids: &mut ImMap<Variable, Variable>, rigids: &mut ImSet<Variable>,
change_var_kind: bool, change_var_kind: bool,
) -> (Vec<Variable>, Vec<Type>) { ) -> (Vec<Variable>, Vec<Type>) {
anns.iter() anns.iter()
@ -1917,14 +1968,20 @@ fn aliases_to_attr_type(var_store: &mut VarStore, aliases: &mut SendMap<Symbol,
// //
// That would give a double attr wrapper on the type arguments. // 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 * // 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 // remove the outer Attr, because when this occurs in a signature it'll already be wrapped in one
match new { match new {
Type::Apply(Symbol::ATTR_ATTR, args) => { Type::Apply(Symbol::ATTR_ATTR, args) => {
alias.typ = args[1].clone(); 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"),
}
if let Some(b) = &alias.uniqueness {
fix_mutual_recursive_alias(&mut alias.typ, b);
} }
} }
} }
@ -2060,9 +2117,9 @@ fn instantiate_rigids(
annotation.substitute(&rigid_substitution); annotation.substitute(&rigid_substitution);
} }
let mut new_rigid_pairs = ImMap::default(); let mut new_uniqueness_rigids = ImSet::default();
let (mut uniq_vars, annotation) = 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 { if let Pattern::Identifier(symbol) = loc_pattern.value {
headers.insert(symbol, Located::at(loc_pattern.region, annotation.clone())); headers.insert(symbol, Located::at(loc_pattern.region, annotation.clone()));
@ -2072,7 +2129,7 @@ fn instantiate_rigids(
) { ) {
for (k, v) in new_headers { for (k, v) in new_headers {
let (new_uniq_vars, attr_annotation) = 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); uniq_vars.extend(new_uniq_vars);
@ -2082,10 +2139,7 @@ fn instantiate_rigids(
new_rigids.extend(uniq_vars); new_rigids.extend(uniq_vars);
new_rigids.extend(introduced_vars.wildcards.iter().cloned()); new_rigids.extend(introduced_vars.wildcards.iter().cloned());
new_rigids.extend(new_uniqueness_rigids);
for (_, v) in new_rigid_pairs {
new_rigids.push(v);
}
annotation annotation
} }
@ -2296,3 +2350,82 @@ fn constrain_field_update(
(var, field_type, con) (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);
tags.iter_mut()
.map(|v| v.1.iter_mut())
.flatten()
.for_each(|arg| 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) => {
fix_mutual_recursive_alias_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

@ -357,7 +357,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
let byte_type = ctx.i8_type(); let byte_type = ctx.i8_type();
let nul_terminator = byte_type.const_zero(); let nul_terminator = byte_type.const_zero();
let len_val = ctx.i32_type().const_int(str_len as u64, false); let len_val = ctx.i64_type().const_int(str_len as u64, false);
let ptr = env let ptr = env
.builder .builder
.build_array_malloc(ctx.i8_type(), len_val, "str_ptr") .build_array_malloc(ctx.i8_type(), len_val, "str_ptr")
@ -367,7 +367,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
// Copy the bytes from the string literal into the array // Copy the bytes from the string literal into the array
for (index, byte) in str_literal.bytes().enumerate() { for (index, byte) in str_literal.bytes().enumerate() {
let index_val = ctx.i32_type().const_int(index as u64, false); let index_val = ctx.i64_type().const_int(index as u64, false);
let elem_ptr = let elem_ptr =
unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "byte") }; unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "byte") };
@ -377,7 +377,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
// Add a NUL terminator at the end. // Add a NUL terminator at the end.
// TODO: Instead of NUL-terminating, return a struct // TODO: Instead of NUL-terminating, return a struct
// with the pointer and also the length and capacity. // with the pointer and also the length and capacity.
let index_val = ctx.i32_type().const_int(str_len as u64 - 1, false); let index_val = ctx.i64_type().const_int(str_len as u64 - 1, false);
let elem_ptr = let elem_ptr =
unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "nul_terminator") }; unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "nul_terminator") };
@ -399,11 +399,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
let builder = env.builder; let builder = env.builder;
if elems.is_empty() { if elems.is_empty() {
let struct_type = collection(ctx, env.ptr_bytes); empty_list(env)
// The pointer should be null (aka zero) and the length should be zero,
// so the whole struct should be a const_zero
BasicValueEnum::StructValue(struct_type.const_zero())
} else { } else {
let len_u64 = elems.len() as u64; let len_u64 = elems.len() as u64;
let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64; let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64;
@ -422,7 +418,7 @@ pub fn build_expr<'a, 'ctx, 'env>(
// Copy the elements from the list literal into the array // Copy the elements from the list literal into the array
for (index, elem) in elems.iter().enumerate() { for (index, elem) in elems.iter().enumerate() {
let index_val = ctx.i32_type().const_int(index as u64, false); let index_val = ctx.i64_type().const_int(index as u64, false);
let elem_ptr = let elem_ptr =
unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") }; unsafe { builder.build_in_bounds_gep(ptr, &[index_val], "index") };
let val = build_expr(env, layout_ids, &scope, parent, &elem); let val = build_expr(env, layout_ids, &scope, parent, &elem);
@ -1018,6 +1014,212 @@ pub fn verify_fn(fn_val: FunctionValue<'_>) {
} }
} }
fn list_single<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
symbol: Symbol,
parent: FunctionValue<'ctx>,
args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)],
) -> BasicValueEnum<'ctx> {
// List.single : a -> List a
debug_assert_eq!(args.len(), 1);
let (elem, elem_layout) = args[0];
let builder = env.builder;
let ctx = env.context;
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64;
let ptr = {
let bytes_len = elem_bytes;
let len_type = env.ptr_int();
let len = len_type.const_int(bytes_len, false);
env.builder
.build_array_malloc(elem_type, len, "create_list_ptr")
.unwrap()
// TODO check if malloc returned null; if so, runtime error for OOM!
};
// Put the element into the list
let elem_ptr = unsafe {
builder.build_in_bounds_gep(
ptr,
&[ctx.i64_type().const_int(
// 0 as in 0 index of our new list
0 as u64, false,
)],
"index",
)
};
builder.build_store(elem_ptr, elem);
let ptr_bytes = env.ptr_bytes;
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(ptr, int_type, "list_cast_ptr");
let struct_type = collection(ctx, ptr_bytes);
let len = BasicValueEnum::IntValue(env.ptr_int().const_int(1, false));
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, len, Builtin::WRAPPER_LEN, "insert_len")
.unwrap();
//
builder.build_bitcast(
struct_val.into_struct_value(),
collection(ctx, ptr_bytes),
"cast_collection",
)
}
fn list_repeat<'a, 'ctx, 'env>(
env: &Env<'a, 'ctx, 'env>,
layout_ids: &mut LayoutIds<'a>,
layout: &Layout<'a>,
symbol: Symbol,
parent: FunctionValue<'ctx>,
args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)],
) -> BasicValueEnum<'ctx> {
// List.repeat : Int, elem -> List elem
debug_assert_eq!(args.len(), 1);
// Number of repeats
let list_len = args[0].0.into_int_value();
let builder = env.builder;
let ctx = env.context;
let (elem, elem_layout) = args[1];
let elem_type = basic_type_from_layout(env.arena, ctx, elem_layout, env.ptr_bytes);
// list_len > 0
// We have to do a loop below, continuously adding the `elem`
// to the output list `List elem` until we have reached the
// number of repeats. This `comparison` is used to check
// if we need to do any looping; because if we dont, then we
// dont need to allocate memory for the index or the check
// if index != 0
let comparison = builder.build_int_compare(
IntPredicate::UGT,
list_len,
ctx.i64_type().const_int(0, false),
"atleastzero",
);
let build_then = || {
// Allocate space for the new array that we'll copy into.
let elem_bytes = elem_layout.stack_size(env.ptr_bytes) as u64;
let list_ptr = {
let bytes_len = elem_bytes;
let len_type = env.ptr_int();
let len = len_type.const_int(bytes_len, false);
env.builder
.build_array_malloc(elem_type, len, "create_list_ptr")
.unwrap()
// TODO check if malloc returned null; if so, runtime error for OOM!
};
let index_name = "#index";
let start_alloca = builder.build_alloca(ctx.i64_type(), index_name);
builder.build_store(start_alloca, list_len);
let loop_bb = ctx.append_basic_block(parent, "loop");
builder.build_unconditional_branch(loop_bb);
builder.position_at_end(loop_bb);
// #index = #index - 1
let curr_index = builder
.build_load(start_alloca, index_name)
.into_int_value();
let next_index =
builder.build_int_sub(curr_index, ctx.i64_type().const_int(1, false), "nextindex");
builder.build_store(start_alloca, next_index);
let elem_ptr =
unsafe { builder.build_in_bounds_gep(list_ptr, &[curr_index], "load_index") };
// Mutate the new array in-place to change the element.
builder.build_store(elem_ptr, elem);
// #index != 0
let end_cond = builder.build_int_compare(
IntPredicate::NE,
ctx.i64_type().const_int(0, false),
curr_index,
"loopcond",
);
let after_bb = ctx.append_basic_block(parent, "afterloop");
builder.build_conditional_branch(end_cond, loop_bb, after_bb);
builder.position_at_end(after_bb);
let ptr_bytes = env.ptr_bytes;
let int_type = ptr_int(ctx, ptr_bytes);
let ptr_as_int = builder.build_ptr_to_int(list_ptr, int_type, "list_cast_ptr");
let struct_type = collection(ctx, 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, list_len, Builtin::WRAPPER_LEN, "insert_len")
.unwrap();
builder.build_bitcast(
struct_val.into_struct_value(),
collection(ctx, ptr_bytes),
"cast_collection",
)
};
let build_else = || empty_list(env);
let struct_type = collection(ctx, env.ptr_bytes);
build_basic_phi2(
env,
parent,
comparison,
build_then,
build_else,
BasicTypeEnum::StructType(struct_type),
)
}
#[inline(always)] #[inline(always)]
#[allow(clippy::cognitive_complexity)] #[allow(clippy::cognitive_complexity)]
fn call_with_args<'a, 'ctx, 'env>( fn call_with_args<'a, 'ctx, 'env>(
@ -1041,7 +1243,6 @@ fn call_with_args<'a, 'ctx, 'env>(
panic!("Unrecognized non-builtin function: {:?}", symbol) panic!("Unrecognized non-builtin function: {:?}", symbol)
} }
}); });
let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena); let mut arg_vals: Vec<BasicValueEnum> = Vec::with_capacity_in(args.len(), env.arena);
for (arg, _layout) in args.iter() { for (arg, _layout) in args.iter() {
@ -1186,6 +1387,16 @@ enum InPlace {
Clone, Clone,
} }
fn empty_list<'a, 'ctx, 'env>(env: &Env<'a, 'ctx, 'env>) -> BasicValueEnum<'ctx> {
let ctx = env.context;
let struct_type = collection(ctx, env.ptr_bytes);
// The pointer should be null (aka zero) and the length should be zero,
// so the whole struct should be a const_zero
BasicValueEnum::StructValue(struct_type.const_zero())
}
fn bounds_check_comparison<'ctx>( fn bounds_check_comparison<'ctx>(
builder: &Builder<'ctx>, builder: &Builder<'ctx>,
elem_index: IntValue<'ctx>, elem_index: IntValue<'ctx>,
@ -1198,6 +1409,100 @@ fn bounds_check_comparison<'ctx>(
builder.build_int_compare(IntPredicate::ULT, elem_index, len, "bounds_check") 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>( fn list_set<'a, 'ctx, 'env>(
parent: FunctionValue<'ctx>, parent: FunctionValue<'ctx>,
args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)], args: &[(BasicValueEnum<'ctx>, &'a Layout<'a>)],

View file

@ -37,17 +37,6 @@ mod gen_builtins {
assert_evals_to!("[ 12, 9, 6, 3 ]", &[12, 9, 6, 3], &'static [i64]); assert_evals_to!("[ 12, 9, 6, 3 ]", &[12, 9, 6, 3], &'static [i64]);
} }
// #[test]
// fn list_push() {
// assert_evals_to!("List.push [] 1", &[1], &'static [i64]);
// }
#[test]
fn list_single() {
assert_evals_to!("List.single 1", &[1], &'static [i64]);
assert_evals_to!("List.single 5.6", &[5.6], &'static [f64]);
}
#[test] #[test]
fn empty_list_len() { fn empty_list_len() {
assert_evals_to!("List.len []", 0, usize); assert_evals_to!("List.len []", 0, usize);
@ -380,4 +369,31 @@ mod gen_builtins {
&'static [i64] &'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() {
assert_evals_to!("List.single 1", &[1], &'static [i64]);
assert_evals_to!("List.single 5.6", &[5.6], &'static [f64]);
}
#[test]
fn list_repeat() {
assert_evals_to!("List.repeat 5 1", &[1, 1, 1, 1, 1], &'static [i64]);
assert_evals_to!("List.repeat 4 2", &[2, 2, 2, 2], &'static [i64]);
assert_evals_to!("List.repeat 0 []", &[], &'static [i64]);
assert_evals_to!("List.repeat 2 []", &[&[], &[]], &'static [&'static [i64]]);
}
} }

View file

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

View file

@ -221,12 +221,12 @@ mod test_uniq_load {
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { 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)))", "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)))", "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 ]*)))", "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 Shared position))), Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr * [ Err (Attr * [ KeyNotFound ]*), Ok (Attr * (List (Attr Shared position))) ]*)", "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))) ]*)",
}, },
); );
}); });
@ -243,8 +243,8 @@ mod test_uniq_load {
loaded_module, loaded_module,
hashmap! { hashmap! {
"swap" => "Attr * (Attr Shared Int, Attr Shared Int, Attr * (List (Attr Shared a)) -> Attr * (List (Attr Shared a)))", "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 c a)))) -> Attr * [ Pair (Attr * Int) (Attr b (List (Attr Shared (Num (Attr c 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 c a)))), Attr Shared Int, Attr Shared Int -> Attr b (List (Attr Shared (Num (Attr c 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)))))",
}, },
); );
}); });
@ -273,6 +273,8 @@ mod test_uniq_load {
let loaded_module = let loaded_module =
load_fixture("interface_with_deps", "Primary", subs_by_module).await; load_fixture("interface_with_deps", "Primary", subs_by_module).await;
// the inferred signature for withDefault is wrong, part of the alias in alias issue.
// "withDefault" => "Attr * (Attr * (Res.Res (Attr a b) (Attr * *)), Attr a b -> Attr a b)",
expect_types( expect_types(
loaded_module, loaded_module,
hashmap! { hashmap! {
@ -285,7 +287,7 @@ mod test_uniq_load {
"w" => "Attr * (Dep1.Identity (Attr * {}))", "w" => "Attr * (Dep1.Identity (Attr * {}))",
"succeed" => "Attr * (Attr b a -> Attr * (Dep1.Identity (Attr b a)))", "succeed" => "Attr * (Attr b a -> Attr * (Dep1.Identity (Attr b a)))",
"yay" => "Attr * (Res.Res (Attr * {}) (Attr * err))", "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 (* | * | *) (Res.Res (Attr * a) (Attr * *)), Attr * a -> Attr * a)",
}, },
); );
}); });

View file

@ -638,6 +638,7 @@ define_builtins! {
0 STR_STR: "Str" imported // the Str.Str type alias 0 STR_STR: "Str" imported // the Str.Str type alias
1 STR_AT_STR: "@Str" // the Str.@Str private tag 1 STR_AT_STR: "@Str" // the Str.@Str private tag
2 STR_ISEMPTY: "isEmpty" 2 STR_ISEMPTY: "isEmpty"
3 STR_APPEND: "append"
} }
4 LIST: "List" => { 4 LIST: "List" => {
0 LIST_LIST: "List" imported // the List.List type alias 0 LIST_LIST: "List" imported // the List.List type alias
@ -653,6 +654,7 @@ define_builtins! {
10 LIST_CONCAT: "concat" 10 LIST_CONCAT: "concat"
11 LIST_FIRST: "first" 11 LIST_FIRST: "first"
12 LIST_SINGLE: "single" 12 LIST_SINGLE: "single"
13 LIST_REPEAT: "repeat"
} }
5 RESULT: "Result" => { 5 RESULT: "Result" => {
0 RESULT_RESULT: "Result" imported // the Result.Result type alias 0 RESULT_RESULT: "Result" imported // the Result.Result type alias

View file

@ -1034,9 +1034,9 @@ mod when {
branch_result(indented_more) branch_result(indented_more)
), ),
|((patterns, guard), expr)| WhenBranch { |((patterns, guard), expr)| WhenBranch {
patterns: patterns, patterns,
value: expr, value: expr,
guard: guard guard
} }
); );

View file

@ -2842,4 +2842,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::ident::TagName;
use roc_module::symbol::Symbol; use roc_module::symbol::Symbol;
use roc_region::all::{Located, Region}; 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::solved_types::Solved;
use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable}; use roc_types::subs::{Content, Descriptor, FlatType, Mark, OptVariable, Rank, Subs, Variable};
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
@ -547,7 +547,7 @@ fn type_to_variable(
EmptyTagUnion => Variable::EMPTY_TAG_UNION, EmptyTagUnion => Variable::EMPTY_TAG_UNION,
// This case is important for the rank of boolean variables // 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) => { Boolean(b) => {
let content = Content::Structure(FlatType::Boolean(b.clone())); let content = Content::Structure(FlatType::Boolean(b.clone()));
@ -650,6 +650,7 @@ fn type_to_variable(
} }
Alias(Symbol::BOOL_BOOL, _, _) => Variable::BOOL, Alias(Symbol::BOOL_BOOL, _, _) => Variable::BOOL,
Alias(symbol, args, alias_type) => { 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) // 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 // different variables (once for each occurence). The recursion restriction is required
// for uniqueness types only: recursive aliases "introduce" an unbound uniqueness // 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 // TODO does caching work at all with uniqueness types? even Int then hides a uniqueness variable
let is_recursive = alias_type.is_recursive(); let is_recursive = alias_type.is_recursive();
let no_args = args.is_empty(); let no_args = args.is_empty();
/*
if no_args && !is_recursive { if no_args && !is_recursive {
if let Some(var) = cached.get(symbol) { if let Some(var) = cached.get(symbol) {
return *var; return *var;
} }
} }
*/
let mut arg_vars = Vec::with_capacity(args.len()); let mut arg_vars = Vec::with_capacity(args.len());
let mut new_aliases = ImMap::default(); let mut new_aliases = ImMap::default();
@ -688,7 +691,7 @@ fn type_to_variable(
let result = register(subs, rank, pools, content); let result = register(subs, rank, pools, content);
if no_args && !is_recursive { if no_args && !is_recursive {
cached.insert(*symbol, result); // cached.insert(*symbol, result);
} }
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),
} }
} }
_ => 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 rank
} }
Boolean(b) => { Boolean(Bool::Shared) => Rank::toplevel(),
let mut rank = Rank::toplevel(); Boolean(Bool::Container(cvar, mvars)) => {
for var in b.variables() { 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)); rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
} }

View file

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

View file

@ -54,7 +54,10 @@ mod solve_uniq_expr {
let (problems, actual) = infer_eq_help(src); let (problems, actual) = infer_eq_help(src);
if !problems.is_empty() { 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()); assert_eq!(actual, expected.to_string());
@ -1080,7 +1083,7 @@ mod solve_uniq_expr {
// TODO: is it safe to ignore uniqueness constraints from patterns that bind no identifiers? // 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? // 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. // 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 solve_uniq_expr {
r 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 solve_uniq_expr {
r 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 solve_uniq_expr {
r.tic.tac.toe 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 (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)"
// "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)"
); );
} }
@ -1475,7 +1477,7 @@ mod solve_uniq_expr {
quicksort quicksort
"# "#
), ),
"Attr Shared (Attr b (List (Attr Shared (Num (Attr c a)))), Attr Shared Int, Attr Shared Int -> Attr b (List (Attr Shared (Num (Attr c a)))))" "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)))))"
); );
}) })
} }
@ -1623,10 +1625,10 @@ mod solve_uniq_expr {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
singleton : p -> [ Cons p (ConsList p), Nil ] as ConsList p singleton : p -> [ Cons p (ConsList p), Nil ] as ConsList p
singleton = \x -> Cons x Nil singleton = \x -> Cons x Nil
singleton singleton
"# "#
), ),
"Attr * (Attr a p -> Attr * (ConsList (Attr a p)))", "Attr * (Attr a p -> Attr * (ConsList (Attr a p)))",
@ -1668,7 +1670,7 @@ mod solve_uniq_expr {
map 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 solve_uniq_expr {
map 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 solve_uniq_expr {
); );
} }
// This snippet exhibits the rank issue. Seems to only occur when using recursive types with
// recursive functions.
#[test] #[test]
fn rigids_in_signature() { fn rigids_in_signature() {
infer_eq( infer_eq(
@ -1744,13 +1744,19 @@ mod solve_uniq_expr {
r#" r#"
ConsList a : [ Cons a (ConsList a), Nil ] ConsList a : [ Cons a (ConsList a), Nil ]
map : (p -> q), p -> ConsList q map : (p -> q), ConsList p -> ConsList q
map = \f, x -> map f x map = \f, list ->
when list is
Cons x xs ->
Cons (f x) (map f xs)
Nil ->
Nil
map 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 solve_uniq_expr {
toEmpty 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 solve_uniq_expr {
toEmpty 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,103 @@ mod solve_uniq_expr {
); );
} }
#[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 assoc_list_map() {
// infer_eq(
// indoc!(
// r#"
// ConsList a : [ Cons a (ConsList a), Nil ]
// AssocList a b : ConsList { key: a, value : b }
//
// map : AssocList k v -> AssocList k v
// map = \list ->
// when list is
// Cons { key, value } xs ->
// Cons {key: key, value: value } xs
//
// Nil ->
// Nil
//
// map
// "#
// ),
// // "Attr Shared (Attr Shared (Attr Shared k, Attr a v -> Attr b v2), Attr (c | d | e) (AssocList (Attr Shared k) (Attr a v)) -> Attr (c | d | e) (AssocList (Attr Shared k) (Attr b v2)))"
// "Attr Shared (Attr Shared (Attr a p -> Attr b q), Attr (* | a) (ConsList (Attr a p)) -> Attr * (ConsList (Attr b q)))",
// );
// }
#[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] #[test]
fn typecheck_mutually_recursive_tag_union() { fn typecheck_mutually_recursive_tag_union() {
infer_eq( infer_eq(
@ -1849,10 +1952,14 @@ mod solve_uniq_expr {
Cons b newLista -> Cons b newLista ->
Cons a (Cons (f b) (toAs f newLista)) Cons a (Cons (f b) (toAs f newLista))
foo = \_ ->
x = 4
{ a : x, b : x }.a
toAs 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)))"
); );
} }
@ -1873,7 +1980,7 @@ mod solve_uniq_expr {
toAs 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)"
); );
} }
@ -1885,12 +1992,12 @@ mod solve_uniq_expr {
4 + 4 4 + 4
"# "#
), ),
"Attr * (Num (Attr * *))", "Attr a (Num (Attr a *))",
); );
} }
#[test] #[test]
fn list_get() { fn list_get_at() {
infer_eq( infer_eq(
indoc!( indoc!(
r#" r#"
@ -2004,7 +2111,7 @@ mod solve_uniq_expr {
list list
"# "#
), ),
"Attr * (Attr a (List (Attr Shared (Num (Attr b c)))) -> Attr a (List (Attr Shared (Num (Attr b c)))))", "Attr * (Attr a (List (Attr Shared (Num (Attr Shared b)))) -> Attr a (List (Attr Shared (Num (Attr Shared b)))))",
); );
} }
@ -2024,19 +2131,6 @@ mod solve_uniq_expr {
); );
} }
#[test]
fn list_set() {
infer_eq(indoc!(r#"List.set"#), "Attr * (Attr (* | a | b) (List (Attr a c)), Attr * Int, Attr (a | b) c -> Attr * (List (Attr a c)))");
}
#[test]
fn list_map() {
infer_eq(
indoc!(r#"List.map"#),
"Attr * (Attr * (List a), Attr Shared (a -> b) -> Attr * (List b))",
);
}
#[test] #[test]
fn list_map_identity() { fn list_map_identity() {
infer_eq( infer_eq(
@ -2045,14 +2139,6 @@ mod solve_uniq_expr {
); );
} }
#[test]
fn list_foldr() {
infer_eq(
indoc!(r#"List.foldr"#),
"Attr * (Attr * (List a), Attr Shared (a, b -> b), b -> b)",
);
}
#[test] #[test]
fn list_foldr_sum() { fn list_foldr_sum() {
infer_eq( infer_eq(
@ -2063,15 +2149,72 @@ mod solve_uniq_expr {
sum sum
"# "#
), ),
"Attr * (Attr * (List (Attr * (Num (Attr a b)))) -> Attr * (Num (Attr a b)))", "Attr * (Attr (* | a) (List (Attr a (Num (Attr a b)))) -> Attr c (Num (Attr c b)))",
);
}
#[test]
fn num_add() {
infer_eq(
"Num.add",
"Attr * (Attr a (Num (Attr a b)), Attr c (Num (Attr c b)) -> Attr d (Num (Attr d b)))",
);
}
#[test]
fn list_isempty() {
infer_eq("List.isEmpty", "Attr * (Attr * (List *) -> Attr * Bool)");
}
#[test]
fn list_len() {
infer_eq("List.len", "Attr * (Attr * (List *) -> Attr * Int)");
}
#[test]
fn list_get() {
infer_eq("List.get", "Attr * (Attr (* | a) (List (Attr a b)), Attr * Int -> Attr * (Result (Attr a b) (Attr * [ OutOfBounds ]*)))");
}
#[test]
fn list_set() {
infer_eq("List.set", "Attr * (Attr (* | a | b) (List (Attr a c)), Attr * Int, Attr (a | b) c -> Attr * (List (Attr a c)))");
}
#[test]
fn list_single() {
infer_eq("List.single", "Attr * (a -> Attr * (List a))");
}
#[test]
fn list_repeat() {
infer_eq(
"List.repeat",
"Attr * (Attr * Int, Attr Shared a -> Attr * (List (Attr Shared a)))",
); );
} }
#[test] #[test]
fn list_push() { fn list_push() {
infer_eq( infer_eq(
indoc!(r#"List.push"#), "List.push",
"Attr * (Attr (* | a | b) (List (Attr a c)), Attr (a | b) c -> Attr * (List (Attr a c)))" "Attr * (Attr * (List a), a -> Attr * (List a))",
);
}
#[test]
fn list_map() {
infer_eq(
"List.map",
"Attr * (Attr * (List a), Attr Shared (a -> b) -> Attr * (List b))",
);
}
#[test]
fn list_foldr() {
infer_eq(
"List.foldr",
"Attr * (Attr (* | a) (List (Attr a b)), Attr Shared (Attr a b, c -> c), c -> c)",
); );
} }
@ -2085,7 +2228,7 @@ mod solve_uniq_expr {
singleton singleton
"# "#
), ),
"Attr * (Attr (* | a) b -> Attr * (List (Attr a b)))", "Attr * (a -> Attr * (List a))",
); );
} }
@ -2095,12 +2238,10 @@ mod solve_uniq_expr {
indoc!( indoc!(
r#" r#"
reverse = \list -> List.foldr list (\e, l -> List.push l e) [] reverse = \list -> List.foldr list (\e, l -> List.push l e) []
reverse reverse
"# "#
), ),
// "Attr * (Attr * (List (Attr (a | b) c)) -> Attr (* | a | b) (List (Attr b c)))", "Attr * (Attr (* | a) (List (Attr a b)) -> Attr * (List (Attr a b)))",
"Attr * (Attr * (List (Attr (a | b) c)) -> Attr (* | a | b) (List (Attr a c)))",
); );
} }
@ -2118,6 +2259,377 @@ mod solve_uniq_expr {
); );
} }
#[test]
fn set_empty() {
infer_eq("Set.empty", "Attr * (Set *)");
}
#[test]
fn set_singelton() {
infer_eq("Set.singleton", "Attr * (a -> Attr * (Set a))");
}
#[test]
fn set_union() {
infer_eq(
"Set.union",
"Attr * (Attr * (Set (Attr * a)), Attr * (Set (Attr * a)) -> Attr * (Set (Attr * a)))",
);
}
#[test]
fn set_diff() {
infer_eq(
"Set.diff",
"Attr * (Attr * (Set (Attr * a)), Attr * (Set (Attr * a)) -> Attr * (Set (Attr * a)))",
);
}
#[test]
fn set_foldl() {
infer_eq(
"Set.foldl",
"Attr * (Attr (* | a) (Set (Attr a b)), Attr Shared (Attr a b, c -> c), c -> c)",
);
}
#[test]
fn set_insert() {
infer_eq("Set.insert", "Attr * (Attr * (Set a), a -> Attr * (Set a))");
}
#[test]
fn set_remove() {
infer_eq(
"Set.remove",
"Attr * (Attr * (Set (Attr a b)), Attr * b -> Attr * (Set (Attr a b)))",
);
}
#[test]
fn map_empty() {
infer_eq("Map.empty", "Attr * (Map * *)");
}
#[test]
fn map_singelton() {
infer_eq("Map.singleton", "Attr * (a, b -> Attr * (Map a b))");
}
#[test]
fn map_get() {
infer_eq("Map.get", "Attr * (Attr (* | a) (Map (Attr * b) (Attr a c)), Attr * b -> Attr * (Result (Attr a c) (Attr * [ KeyNotFound ]*)))");
}
#[test]
fn map_insert() {
infer_eq(
"Map.insert",
"Attr * (Attr * (Map a b), a, b -> Attr * (Map a b))",
);
}
#[test]
fn str_is_empty() {
infer_eq("Str.isEmpty", "Attr * (Attr * Str -> Attr * Bool)");
}
#[test]
fn str_append() {
infer_eq(
"Str.append",
"Attr * (Attr * Str, Attr * Str -> Attr * Str)",
);
}
#[test]
fn result_map() {
infer_eq(
"Result.map",
"Attr * (Attr * (Result a b), Attr * (a -> c) -> Attr * (Result c b))",
);
}
#[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] #[test]
fn use_correct_ext_var() { fn use_correct_ext_var() {
infer_eq( infer_eq(
@ -2154,7 +2666,7 @@ mod solve_uniq_expr {
reconstructPath 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)))"
); );
} }
@ -2196,7 +2708,7 @@ mod solve_uniq_expr {
cheapestOpen 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 ]*)))"
) )
}); });
} }
@ -2363,20 +2875,24 @@ mod solve_uniq_expr {
findPath 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 ]*)))"
) )
}); });
} }
#[test] #[test]
fn equals() { fn bool_eq() {
infer_eq( infer_eq(
indoc!( "\\a, b -> a == b",
r#" "Attr * (Attr * a, Attr * a -> Attr * Bool)",
\a, b -> a == b );
"# }
),
"Attr * (a, a -> Attr * Bool)", #[test]
fn bool_neq() {
infer_eq(
"\\a, b -> a != b",
"Attr * (Attr * a, Attr * a -> Attr * Bool)",
); );
} }
@ -2411,7 +2927,7 @@ mod solve_uniq_expr {
_ -> 3 _ -> 3
"# "#
), ),
"Attr * (Attr Shared (Num (Attr * *)) -> Attr * (Num (Attr * *)))", "Attr * (Attr Shared (Num (Attr Shared *)) -> Attr * (Num (Attr * *)))",
); );
} }
} }

View file

@ -1,137 +1,182 @@
use self::Atom::*; use self::Bool::*;
use crate::subs::{Content, FlatType, Subs, Variable}; use crate::subs::{Content, FlatType, Subs, Variable};
use roc_collections::all::SendSet; use roc_collections::all::SendSet;
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] /// Uniqueness types
pub struct Bool(pub Atom, pub SendSet<Atom>); ///
/// 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)] #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum Atom { pub enum Bool {
Zero, Shared,
One, Container(Variable, SendSet<Variable>),
Variable(Variable),
} }
impl Atom { pub fn var_is_shared(subs: &Subs, var: Variable) -> bool {
pub fn apply_subs(&mut self, subs: &mut Subs) { match subs.get_without_compacting(var).content {
match self { Content::Structure(FlatType::Boolean(Bool::Shared)) => true,
Atom::Zero | Atom::One => {} _ => false,
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; /// 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 { result
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,
},
}
}
} }
impl Bool { impl Bool {
pub fn shared() -> Self { pub fn shared() -> Self {
Bool(Zero, SendSet::default()) Bool::Shared
} }
pub fn unique() -> Self { pub fn container<I>(cvar: Variable, mvars: I) -> Self
Bool(One, SendSet::default()) where
I: IntoIterator<Item = Variable>,
{
Bool::Container(cvar, mvars.into_iter().collect())
} }
pub fn variable(variable: Variable) -> Self { pub fn variable(var: Variable) -> Self {
Bool(Variable(variable), SendSet::default()) Bool::Container(var, SendSet::default())
} }
pub fn with_free(variable: Variable, rest: Vec<Atom>) -> Self { pub fn is_fully_simplified(&self, subs: &Subs) -> bool {
let atom_set: SendSet<Atom> = rest.into_iter().collect(); match self {
Bool(Variable(variable), atom_set) 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 { pub fn is_unique(&self, subs: &Subs) -> bool {
let atom_set: SendSet<Atom> = rest.into_iter().collect(); match self.simplify(subs) {
Bool(free, atom_set) Shared => false,
_ => true,
}
} }
pub fn variables(&self) -> SendSet<Variable> { 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 { mvars
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)
}
}
} }
} }
} }
@ -140,28 +185,33 @@ impl Bool {
where where
F: FnMut(Variable) -> Variable, 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 { Bool::Container(new_cvar, new_mvars)
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(new_free, new_bound)
} }
pub fn is_unique(&self, subs: &Subs) -> bool { pub fn simplify(&self, subs: &Subs) -> Self {
match self.simplify(subs) { match self {
Ok(_variables) => true, Bool::Container(cvar, mvars) => {
Err(atom) => atom.is_unique(subs), 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::subs::{Content, FlatType, Subs, Variable};
use crate::types::name_type_var; 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::ident::{Lowercase, TagName};
use roc_module::symbol::{Interns, ModuleId, Symbol}; use roc_module::symbol::{Interns, ModuleId, Symbol};
@ -77,28 +77,34 @@ fn find_names_needed(
use crate::subs::Content::*; use crate::subs::Content::*;
use crate::subs::FlatType::*; use crate::subs::FlatType::*;
while let Some((recursive, _)) = subs.occurs(variable) { while let Some((recursive, _chain)) = subs.occurs(variable) {
if let Content::Structure(FlatType::TagUnion(tags, ext_var)) = let content = subs.get_without_compacting(recursive).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 rec_var = subs.fresh_unnamed_flex_var();
let mut new_tags = MutMap::default(); let mut new_tags = MutMap::default();
for (label, args) in tags { for (label, args) in tags {
let new_args = args let new_args = args
.clone() .clone()
.into_iter() .into_iter()
.map(|var| if var == recursive { rec_var } else { var }) .map(|var| if var == recursive { rec_var } else { var })
.collect(); .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));
} }
Content::Structure(FlatType::Boolean(Bool::Container(_cvar, _mvars))) => {
let flat_type = FlatType::RecursiveTagUnion(rec_var, new_tags, ext_var); crate::boolean_algebra::flatten(subs, recursive);
subs.set_content(recursive, Content::Structure(flat_type)); }
} else { _ => panic!(
panic!("unfixable recursive type in roc_types::pretty_print") "unfixable recursive type in roc_types::pretty_print {:?} {:?} {:?}",
recursive, variable, content
),
} }
} }
@ -168,23 +174,15 @@ fn find_names_needed(
find_names_needed(ext_var, subs, roots, root_appearances, names_taken); find_names_needed(ext_var, subs, roots, root_appearances, names_taken);
find_names_needed(rec_var, subs, roots, root_appearances, names_taken); find_names_needed(rec_var, subs, roots, root_appearances, names_taken);
} }
Structure(Boolean(b)) => Structure(Boolean(b)) => match b {
// NOTE it's important that we traverse the variables in the same order as they are Bool::Shared => {}
// below in write_boolean, hence the call to `simplify`. Bool::Container(cvar, mvars) => {
{ find_names_needed(cvar, subs, roots, root_appearances, names_taken);
match b.simplify(subs) { for var in mvars {
Err(Atom::Variable(var)) => {
find_names_needed(var, subs, roots, root_appearances, names_taken); 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) => { Alias(symbol, args, _actual) => {
if let Symbol::ATTR_ATTR = symbol { if let Symbol::ATTR_ATTR = symbol {
find_names_needed(args[0].1, subs, roots, root_appearances, names_taken); find_names_needed(args[0].1, subs, roots, root_appearances, names_taken);
@ -212,6 +210,8 @@ pub fn name_all_type_vars(variable: Variable, subs: &mut Subs) {
find_names_needed(variable, subs, &mut roots, &mut appearances, &mut taken); find_names_needed(variable, subs, &mut roots, &mut appearances, &mut taken);
for root in roots { 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) { if let Some(Appearances::Multiple) = appearances.get(&root) {
letters_used = name_root(letters_used, root, subs, &mut taken); letters_used = name_root(letters_used, root, subs, &mut taken);
} }
@ -332,6 +332,15 @@ fn write_content(env: &Env, content: Content, subs: &Subs, buf: &mut String, par
Parens::InTypeParam, Parens::InTypeParam,
); );
} }
// useful for debugging
let write_out_alias = false;
if write_out_alias {
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 +594,33 @@ pub fn chase_ext_record(
} }
fn write_boolean(env: &Env, boolean: Bool, subs: &Subs, buf: &mut String, parens: Parens) { fn write_boolean(env: &Env, boolean: Bool, subs: &Subs, buf: &mut String, parens: Parens) {
match boolean.simplify(subs) { use crate::boolean_algebra::var_is_shared;
Err(atom) => write_boolean_atom(env, atom, subs, buf, parens),
Ok(mut variables) => { match boolean.simplify(subs) {
variables.sort(); Bool::Shared => {
let mut buffers_set = ImSet::default(); 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(); let mut inner_buf: String = "".to_string();
write_content( write_content(
env, env,
@ -600,41 +629,25 @@ fn write_boolean(env: &Env, boolean: Bool, subs: &Subs, buf: &mut String, parens
&mut inner_buf, &mut inner_buf,
parens, 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(); buffers.sort();
let combined = buffers.join(" | "); let combined = buffers.join(" | ");
let write_parens = buffers.len() > 1; buf.push_str("(");
write_content(
if write_parens { env,
buf.push_str("("); subs.get_without_compacting(cvar).content,
} subs,
buf,
Parens::Unnecessary,
);
buf.push_str(" | ");
buf.push_str(&combined); buf.push_str(&combined);
if write_parens { buf.push_str(")");
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");
} }
} }
} }
@ -701,7 +714,7 @@ fn write_apply(
_ => default_case(subs, arg_content), _ => default_case(subs, arg_content),
}, },
_ => default_case(subs, arg_content), _other => default_case(subs, arg_content),
}, },
_ => default_case(subs, arg_content), _ => default_case(subs, arg_content),
}, },

View file

@ -49,28 +49,29 @@ pub enum SolvedType {
Alias(Symbol, Vec<(Lowercase, SolvedType)>, Box<SolvedType>), Alias(Symbol, Vec<(Lowercase, SolvedType)>, Box<SolvedType>),
/// a boolean algebra Bool /// a boolean algebra Bool
Boolean(SolvedAtom, Vec<SolvedAtom>), Boolean(SolvedBool),
/// A type error /// A type error
Error, Error,
} }
#[derive(Debug, Clone, PartialEq)] #[derive(Debug, Clone, PartialEq)]
pub enum SolvedAtom { pub enum SolvedBool {
Zero, SolvedShared,
One, SolvedContainer(VarId, Vec<VarId>),
Variable(VarId),
} }
impl SolvedAtom { impl SolvedBool {
pub fn from_atom(atom: boolean_algebra::Atom) -> Self { pub fn from_bool(boolean: &boolean_algebra::Bool) -> Self {
use boolean_algebra::Atom::*; use boolean_algebra::Bool;
// NOTE we blindly trust that `var` is a root and has a FlexVar as content // NOTE we blindly trust that `cvar` is a root and has a FlexVar as content
match atom { match boolean {
Zero => SolvedAtom::Zero, Bool::Shared => SolvedBool::SolvedShared,
One => SolvedAtom::One, Bool::Container(cvar, mvars) => SolvedBool::SolvedContainer(
Variable(var) => SolvedAtom::Variable(VarId::from_var(var)), VarId::from_var(*cvar),
mvars.iter().map(|mvar| VarId::from_var(*mvar)).collect(),
),
} }
} }
} }
@ -170,15 +171,7 @@ impl SolvedType {
SolvedType::Alias(symbol, solved_args, Box::new(solved_type)) SolvedType::Alias(symbol, solved_args, Box::new(solved_type))
} }
Boolean(val) => { Boolean(val) => SolvedType::Boolean(SolvedBool::from_bool(&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)
}
Variable(var) => Self::from_var(solved_subs.inner(), var), Variable(var) => Self::from_var(solved_subs.inner(), var),
} }
} }
@ -281,15 +274,7 @@ impl SolvedType {
} }
EmptyRecord => SolvedType::EmptyRecord, EmptyRecord => SolvedType::EmptyRecord,
EmptyTagUnion => SolvedType::EmptyTagUnion, EmptyTagUnion => SolvedType::EmptyTagUnion,
Boolean(val) => { Boolean(val) => SolvedType::Boolean(SolvedBool::from_bool(&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)
}
Erroneous(problem) => SolvedType::Erroneous(problem), Erroneous(problem) => SolvedType::Erroneous(problem),
} }
} }

View file

@ -892,13 +892,26 @@ where
} }
fn var_to_err_type(subs: &mut Subs, state: &mut ErrorTypeState, var: Variable) -> ErrorType { 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 { if desc.mark == Mark::OCCURS {
ErrorType::Infinite ErrorType::Infinite
} else { } else {
subs.set_mark(var, Mark::OCCURS); 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); let err_type = content_to_err_type(subs, state, var, desc.content);
subs.set_mark(var, desc.mark); subs.set_mark(var, desc.mark);

View file

@ -127,20 +127,17 @@ impl fmt::Debug for Type {
write!(f, " ")?; write!(f, " ")?;
} }
let mut any_written_yet = false; let mut it = tags.iter().peekable();
while let Some((label, arguments)) = it.next() {
for (label, arguments) in tags {
if any_written_yet {
write!(f, ", ")?;
} else {
any_written_yet = true;
}
write!(f, "{:?}", label)?; write!(f, "{:?}", label)?;
for argument in arguments { for argument in arguments {
write!(f, " {:?}", argument)?; write!(f, " {:?}", argument)?;
} }
if it.peek().is_some() {
write!(f, ", ")?;
}
} }
if !tags.is_empty() { if !tags.is_empty() {
@ -171,20 +168,17 @@ impl fmt::Debug for Type {
write!(f, " ")?; write!(f, " ")?;
} }
let mut any_written_yet = false; let mut it = tags.iter().peekable();
while let Some((label, arguments)) = it.next() {
for (label, arguments) in tags {
if any_written_yet {
write!(f, ", ")?;
} else {
any_written_yet = true;
}
write!(f, "{:?}", label)?; write!(f, "{:?}", label)?;
for argument in arguments { for argument in arguments {
write!(f, " {:?}", argument)?; write!(f, " {:?}", argument)?;
} }
if it.peek().is_some() {
write!(f, ", ")?;
}
} }
if !tags.is_empty() { if !tags.is_empty() {
@ -375,6 +369,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> { pub fn symbols(&self) -> ImSet<Symbol> {
let mut found_symbols = ImSet::default(); let mut found_symbols = ImSet::default();
symbols_help(self, &mut found_symbols); symbols_help(self, &mut found_symbols);
@ -428,6 +452,35 @@ impl Type {
actual_type.instantiate_aliases(region, aliases, var_store, introduced); actual_type.instantiate_aliases(region, aliases, var_store, introduced);
} }
Apply(Symbol::ATTR_ATTR, attr_args) => {
use boolean_algebra::Bool;
let mut substitution = ImMap::default();
if let Apply(symbol, _) = attr_args[1] {
if let Some(alias) = aliases.get(&symbol) {
if let Some(Bool::Container(unbound_cvar, mvars1)) =
alias.uniqueness.clone()
{
debug_assert!(mvars1.is_empty());
if let Type::Boolean(Bool::Container(bound_cvar, mvars2)) =
&attr_args[0]
{
debug_assert!(mvars2.is_empty());
substitution.insert(unbound_cvar, Type::Variable(*bound_cvar));
}
}
}
}
for x in attr_args {
x.instantiate_aliases(region, aliases, var_store, introduced);
if !substitution.is_empty() {
x.substitute(&substitution);
}
}
}
Apply(symbol, args) => { Apply(symbol, args) => {
if let Some(alias) = aliases.get(symbol) { if let Some(alias) = aliases.get(symbol) {
if args.len() != alias.vars.len() { if args.len() != alias.vars.len() {
@ -460,9 +513,44 @@ impl Type {
substitution.insert(*placeholder, filler); 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() { for variable in actual.variables() {
if !substitution.contains_key(&variable) { 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(); let var = var_store.fresh();
substitution.insert(variable, Type::Variable(var)); substitution.insert(variable, Type::Variable(var));
@ -698,6 +786,7 @@ pub enum PatternCategory {
pub struct Alias { pub struct Alias {
pub region: Region, pub region: Region,
pub vars: Vec<Located<(Lowercase, Variable)>>, pub vars: Vec<Located<(Lowercase, Variable)>>,
pub uniqueness: Option<boolean_algebra::Bool>,
pub typ: Type, pub typ: Type,
} }
@ -727,7 +816,7 @@ pub enum Mismatch {
CanonicalizationProblem, CanonicalizationProblem,
} }
#[derive(PartialEq, Eq, Debug, Clone)] #[derive(PartialEq, Eq, Clone)]
pub enum ErrorType { pub enum ErrorType {
Infinite, Infinite,
Type(Symbol, Vec<ErrorType>), Type(Symbol, Vec<ErrorType>),
@ -742,6 +831,13 @@ pub enum ErrorType {
Error, 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 { impl ErrorType {
pub fn unwrap_alias(self) -> ErrorType { pub fn unwrap_alias(self) -> ErrorType {
match self { match self {
@ -858,6 +954,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::NUM_INTEGER, _) => {
buf.push_str("Int");
}
Type(Symbol::NUM_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)] #[derive(PartialEq, Eq, Debug, Clone)]
pub enum TypeExt { pub enum TypeExt {
Closed, Closed,

View file

@ -1,7 +1,7 @@
use roc_collections::all::{relative_complement, union, MutMap, SendSet}; use roc_collections::all::{relative_complement, union, MutMap, SendSet};
use roc_module::ident::{Lowercase, TagName}; use roc_module::ident::{Lowercase, TagName};
use roc_module::symbol::Symbol; 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::Content::{self, *};
use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable}; use roc_types::subs::{Descriptor, FlatType, Mark, OptVariable, Subs, Variable};
use roc_types::types::{gather_fields, ErrorType, Mismatch, RecordStructure}; use roc_types::types::{gather_fields, ErrorType, Mismatch, RecordStructure};
@ -66,7 +66,7 @@ macro_rules! mismatch {
type Pool = Vec<Variable>; type Pool = Vec<Variable>;
struct Context { pub struct Context {
first: Variable, first: Variable,
first_desc: Descriptor, first_desc: Descriptor,
second: Variable, 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 { 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 { 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), RigidVar(name) => unify_rigid(subs, &ctx, name, &ctx.second_desc.content),
Structure(flat_type) => { Structure(flat_type) => {
unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content) unify_structure(subs, pool, &ctx, flat_type, &ctx.second_desc.content)
@ -193,16 +206,8 @@ fn unify_structure(
) -> Outcome { ) -> Outcome {
match other { match other {
FlexVar(_) => { FlexVar(_) => {
// TODO special-case boolean here // If the other is flex, Structure wins!
match flat_type { merge(subs, ctx, Structure(flat_type.clone()))
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()))
}
}
} }
RigidVar(name) => { RigidVar(name) => {
// Type mismatch! Rigid can only unify with flex. // Type mismatch! Rigid can only unify with flex.
@ -529,7 +534,29 @@ fn unify_shared_tags(
let expected_len = expected_vars.len(); let expected_len = expected_vars.len();
for (actual, expected) in actual_vars.into_iter().zip(expected_vars.into_iter()) { for (actual, expected) in actual_vars.into_iter().zip(expected_vars.into_iter()) {
let problems = unify_pool(subs, pool, actual, expected); // NOTE the arguments of a tag can be recursive. For instance in the expression
//
// Cons 1 (Cons "foo" Nil)
//
// We need to not just check the outer layer (inferring ConsList Int)
// but also the inner layer (finding a type error, as desired)
//
// This correction introduces the same issue as in https://github.com/elm/compiler/issues/1964
// Polymorphic recursion is now a type error.
let problems = if let Some(rvar) = recursion_var {
if expected == rvar {
unify_pool(subs, pool, actual, ctx.second)
} else {
// replace the rvar with ctx.second in expected
subs.explicit_substitute(rvar, ctx.second, expected);
unify_pool(subs, pool, actual, expected)
}
} else {
// we always unify NonRecursive with Recursive, so this should never happen
debug_assert!(Some(actual) != recursion_var);
unify_pool(subs, pool, actual, expected)
};
if problems.is_empty() { if problems.is_empty() {
matching_vars.push(actual); matching_vars.push(actual);
@ -611,6 +638,10 @@ fn unify_flat_type(
unify_tag_union(subs, pool, ctx, union1, union2, (None, None)) 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)) => { (TagUnion(tags1, ext1), RecursiveTagUnion(recursion_var, tags2, ext2)) => {
let union1 = gather_tags(subs, tags1.clone(), *ext1); let union1 = gather_tags(subs, tags1.clone(), *ext1);
let union2 = gather_tags(subs, tags2.clone(), *ext2); let union2 = gather_tags(subs, tags2.clone(), *ext2);
@ -632,34 +663,69 @@ fn unify_flat_type(
unify_tag_union(subs, pool, ctx, union1, union2, (Some(*rec1), Some(*rec2))) unify_tag_union(subs, pool, ctx, union1, union2, (Some(*rec1), Some(*rec2)))
} }
(Boolean(Bool(free1, rest1)), Boolean(Bool(free2, rest2))) => { (Boolean(b1), Boolean(b2)) => {
// unify the free variables use Bool::*;
let (new_free, mut free_var_problems) = unify_free_atoms(subs, pool, *free1, *free2); 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 for mvar in mvars {
.clone() outcome.extend(unify_pool(subs, pool, ctx.first, *mvar));
.into_iter() }
.chain(rest2.clone().into_iter())
.collect::<SendSet<Atom>>();
let mut combined = if let Err(false) = chase_atom(subs, new_free) { // set the first and second variables to Shared
// if the container is shared, all elements must be shared too let content = Content::Structure(FlatType::Boolean(Bool::Shared));
for atom in combined_rest { outcome.extend(merge(subs, ctx, content));
let (_, atom_problems) = unify_free_atoms(subs, pool, atom, Atom::Zero);
free_var_problems.extend(atom_problems); outcome
} }
Bool(Atom::Zero, SendSet::default()) (Container(cvar, mvars), Shared) => {
} else { let mut outcome = vec![];
Bool(new_free, combined_rest) // 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 // set the first and second variables to Shared
let content = Content::Structure(FlatType::Boolean(combined)); let content = Content::Structure(FlatType::Boolean(Bool::Shared));
merge(subs, ctx, content); 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 => { (Apply(l_symbol, l_args), Apply(r_symbol, r_args)) if l_symbol == r_symbol => {
@ -693,41 +759,6 @@ fn unify_flat_type(
} }
} }
fn chase_atom(subs: &mut Subs, atom: Atom) -> Result<Variable, bool> {
match atom {
Atom::Zero => Err(false),
Atom::One => Err(true),
Atom::Variable(var) => match subs.get(var).content {
Content::Structure(FlatType::Boolean(Bool(first, rest))) => {
debug_assert!(rest.is_empty());
chase_atom(subs, first)
}
_ => Ok(var),
},
}
}
fn unify_free_atoms(subs: &mut Subs, pool: &mut Pool, b1: Atom, b2: Atom) -> (Atom, Vec<Mismatch>) {
match (b1, b2) {
(Atom::Variable(v1), Atom::Variable(v2)) => {
(Atom::Variable(v1), unify_pool(subs, pool, v1, v2))
}
(Atom::Variable(var), other) | (other, Atom::Variable(var)) => {
subs.set_content(
var,
Content::Structure(FlatType::Boolean(Bool(other, SendSet::default()))),
);
(other, vec![])
}
(Atom::Zero, Atom::Zero) => (Atom::Zero, vec![]),
(Atom::One, Atom::One) => (Atom::One, vec![]),
_ => unreachable!(
"invalid boolean unification. Because we never infer One, this should never happen!"
),
}
}
fn unify_zip<'a, I>(subs: &mut Subs, pool: &mut Pool, left_iter: I, right_iter: I) -> Outcome fn unify_zip<'a, I>(subs: &mut Subs, pool: &mut Pool, left_iter: I, right_iter: I) -> Outcome
where where
I: Iterator<Item = &'a Variable>, I: Iterator<Item = &'a Variable>,
@ -765,7 +796,6 @@ fn unify_rigid(subs: &mut Subs, ctx: &Context, name: &Lowercase, other: &Content
#[inline(always)] #[inline(always)]
fn unify_flex( fn unify_flex(
subs: &mut Subs, subs: &mut Subs,
pool: &mut Pool,
ctx: &Context, ctx: &Context,
opt_name: &Option<Lowercase>, opt_name: &Option<Lowercase>,
other: &Content, other: &Content,
@ -776,10 +806,6 @@ fn unify_flex(
merge(subs, ctx, FlexVar(opt_name.clone())) 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(_, _, _) => { FlexVar(Some(_)) | RigidVar(_) | Structure(_) | Alias(_, _, _) => {
// TODO special-case boolean here // TODO special-case boolean here
// In all other cases, if left is flex, defer to right. // In all other cases, if left is flex, defer to right.
@ -791,7 +817,7 @@ fn unify_flex(
} }
} }
fn merge(subs: &mut Subs, ctx: &Context, content: Content) -> Outcome { pub fn merge(subs: &mut Subs, ctx: &Context, content: Content) -> Outcome {
let rank = ctx.first_desc.rank.min(ctx.second_desc.rank); let rank = ctx.first_desc.rank.min(ctx.second_desc.rank);
let desc = Descriptor { let desc = Descriptor {
content, content,

View file

@ -129,11 +129,11 @@ fn run_event_loop() -> Result<(), Box<dyn Error>> {
.. ..
} => { } => {
match ch { match ch {
'\u{8}' => { '\u{8}' | '\u{7f}' => {
// In Linux, we get one of these when you press // In Linux, we get a '\u{8}' when you press backspace,
// backspace, but in macOS we don't. In both, we // but in macOS we get '\u{7f}'. In both, we
// get a Back keydown event. Therefore, we use the // get a Back keydown event. Therefore, we use the
// Back keydown event and ignore this, resulting // Back keydown event and ignore these, resulting
// in a system that works in both Linux and macOS. // in a system that works in both Linux and macOS.
} }
'\u{e000}'..='\u{f8ff}' '\u{e000}'..='\u{f8ff}'