mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 16:21:11 +00:00
Merge trunk
This commit is contained in:
parent
baa3debae2
commit
8c96d12661
26 changed files with 2600 additions and 1093 deletions
|
@ -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!
|
||||||
|
|
|
@ -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
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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(_) => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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>)],
|
||||||
|
|
|
@ -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]]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
|
@ -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)",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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 * *)))",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
},
|
},
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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}'
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue