diff --git a/compiler/builtins/src/std.rs b/compiler/builtins/src/std.rs index 3f7d04aff3..ddd4d08c38 100644 --- a/compiler/builtins/src/std.rs +++ b/compiler/builtins/src/std.rs @@ -6,7 +6,7 @@ use roc_types::solved_types::{BuiltinAlias, SolvedType}; use roc_types::subs::VarId; use std::collections::HashMap; -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum Mode { Standard, Uniqueness, diff --git a/compiler/builtins/src/unique.rs b/compiler/builtins/src/unique.rs index b5c31ec6d5..30537cce8f 100644 --- a/compiler/builtins/src/unique.rs +++ b/compiler/builtins/src/unique.rs @@ -25,6 +25,22 @@ const UVAR4: VarId = VarId::from_u32(1004); const UVAR5: VarId = VarId::from_u32(1005); const UVAR6: VarId = VarId::from_u32(1006); +pub struct IDStore(u32); + +impl IDStore { + fn new() -> Self { + IDStore(2000) + } + + fn fresh(&mut self) -> VarId { + let result = VarId::from_u32(self.0); + + self.0 += 1; + + result + } +} + fn shared() -> SolvedType { SolvedType::Boolean(SolvedAtom::Zero, vec![]) } @@ -207,16 +223,6 @@ pub fn aliases() -> MutMap { }, ); - // List a : [ @List a ] - add_alias( - Symbol::LIST_LIST, - BuiltinAlias { - region: Region::zero(), - vars: vec![Located::at(Region::zero(), "elem".into())], - typ: single_private_tag(Symbol::LIST_AT_LIST, vec![flex(TVAR1)]), - }, - ); - // Result a e : [ Ok a, Err e ] add_alias( Symbol::RESULT_RESULT, @@ -236,6 +242,39 @@ pub fn aliases() -> MutMap { }, ); + // List a : [ @List a ] + add_alias( + Symbol::LIST_LIST, + BuiltinAlias { + region: Region::zero(), + vars: vec![Located::at(Region::zero(), "elem".into())], + typ: single_private_tag(Symbol::LIST_AT_LIST, vec![flex(TVAR1)]), + }, + ); + + // Map key value : [ @Map key value ] + add_alias( + Symbol::MAP_MAP, + BuiltinAlias { + region: Region::zero(), + vars: vec![ + Located::at(Region::zero(), "key".into()), + Located::at(Region::zero(), "value".into()), + ], + typ: single_private_tag(Symbol::MAP_AT_MAP, vec![flex(TVAR1), flex(TVAR2)]), + }, + ); + + // Set key : [ @Set key ] + add_alias( + Symbol::SET_SET, + BuiltinAlias { + region: Region::zero(), + vars: vec![Located::at(Region::zero(), "key".into())], + typ: single_private_tag(Symbol::SET_AT_SET, vec![flex(TVAR1)]), + }, + ); + // Str : [ @Str ] add_alias( Symbol::STR_STR, @@ -342,6 +381,12 @@ pub fn types() -> MutMap { ), ); + // toFloat : Num a -> Float + add_type( + Symbol::NUM_TO_FLOAT, + unique_function(vec![num_type(UVAR1, TVAR1)], float_type(UVAR2)), + ); + // Int module // highest : Int @@ -396,6 +441,18 @@ pub fn types() -> MutMap { // Bool module + // isEq or (==) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool + add_type( + Symbol::BOOL_EQ, + unique_function(vec![bool_type(UVAR1), bool_type(UVAR2)], bool_type(UVAR3)), + ); + + // isNeq or (!=) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool + add_type( + Symbol::BOOL_NEQ, + unique_function(vec![bool_type(UVAR1), bool_type(UVAR2)], bool_type(UVAR3)), + ); + // and or (&&) : Attr u1 Bool, Attr u2 Bool -> Attr u3 Bool add_type( Symbol::BOOL_AND, @@ -557,6 +614,267 @@ pub fn types() -> MutMap { ), ); + // Map module + + // empty : Map k v + add_type(Symbol::MAP_EMPTY, map_type(UVAR1, TVAR1, TVAR2)); + + // singleton : k, v -> Map k v + add_type( + Symbol::MAP_SINGLETON, + unique_function( + vec![flex(TVAR1), flex(TVAR2)], + map_type(UVAR1, TVAR1, TVAR2), + ), + ); + + // get : Attr (u | v | *) (Map (Attr u key) (Attr v val), (Attr * key) -> Attr * (Result (Attr v val) [ KeyNotFound ]*) + let key_not_found = SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + SolvedType::Wildcard, + SolvedType::TagUnion( + vec![(TagName::Global("KeyNotFound".into()), vec![])], + Box::new(SolvedType::Wildcard), + ), + ], + ); + + add_type(Symbol::MAP_GET, { + let mut store = IDStore::new(); + + let u = store.fresh(); + let v = store.fresh(); + let key = store.fresh(); + let val = store.fresh(); + let star1 = store.fresh(); + let star2 = store.fresh(); + let star3 = store.fresh(); + + unique_function( + vec![ + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + disjunction(star1, vec![u, v]), + SolvedType::Apply( + Symbol::MAP_MAP, + vec![attr_type(u, key), attr_type(v, val)], + ), + ], + ), + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![disjunction(star2, vec![u]), flex(key)], + ), + ], + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + flex(star3), + SolvedType::Apply( + Symbol::RESULT_RESULT, + vec![attr_type(v, val), key_not_found], + ), + ], + ), + ) + }); + + // insert : Attr (u | v | *) (Map (Attr u key) (Attr v val)), Attr (u | *) key, Attr (v | *) val -> Attr * (Map (Attr u key) (Attr v val)) + add_type(Symbol::MAP_INSERT, { + let mut store = IDStore::new(); + + let u = store.fresh(); + let v = store.fresh(); + let key = store.fresh(); + let val = store.fresh(); + let star1 = store.fresh(); + let star2 = store.fresh(); + let star3 = store.fresh(); + + unique_function( + vec![ + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + disjunction(star1, vec![u, v]), + SolvedType::Apply( + Symbol::MAP_MAP, + vec![attr_type(u, key), attr_type(v, val)], + ), + ], + ), + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![disjunction(star2, vec![u]), flex(key)], + ), + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![disjunction(star2, vec![v]), flex(val)], + ), + ], + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + flex(star3), + SolvedType::Apply(Symbol::MAP_MAP, vec![attr_type(u, key), attr_type(v, val)]), + ], + ), + ) + }); + + // Set module + + // empty : Set a + add_type(Symbol::SET_EMPTY, set_type(UVAR1, TVAR1)); + + // singleton : a -> Set a + add_type( + Symbol::SET_SINGLETON, + unique_function(vec![flex(TVAR1)], set_type(UVAR1, TVAR1)), + ); + + // op : Attr (u | *) (Set (Attr u a)), Attr (u | *) (Set (Attr u a)) -> Attr * Set (Attr u a) + let set_combine = { + let mut store = IDStore::new(); + + let u = store.fresh(); + let a = store.fresh(); + let star1 = store.fresh(); + let star2 = store.fresh(); + let star3 = store.fresh(); + + unique_function( + vec![ + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + disjunction(star1, vec![u]), + SolvedType::Apply(Symbol::SET_SET, vec![attr_type(u, a)]), + ], + ), + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + disjunction(star2, vec![u]), + SolvedType::Apply(Symbol::SET_SET, vec![attr_type(u, a)]), + ], + ), + ], + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + flex(star3), + SolvedType::Apply(Symbol::SET_SET, vec![attr_type(u, a)]), + ], + ), + ) + }; + + // union : Set a, Set a -> Set a + add_type(Symbol::SET_UNION, set_combine.clone()); + + // diff : Set a, Set a -> Set a + add_type(Symbol::SET_DIFF, set_combine); + + // foldl : Attr (u | *) (Set (Attr u a)), Attr Shared (Attr u a -> b -> b), b -> b + add_type(Symbol::SET_FOLDL, { + let mut store = IDStore::new(); + + let u = store.fresh(); + let a = store.fresh(); + let b = store.fresh(); + let star1 = store.fresh(); + + unique_function( + vec![ + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + disjunction(star1, vec![u]), + SolvedType::Apply(Symbol::SET_SET, vec![attr_type(u, a)]), + ], + ), + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + shared(), + SolvedType::Func(vec![attr_type(u, a), flex(b)], Box::new(flex(b))), + ], + ), + flex(b), + ], + flex(b), + ) + }); + + // insert : Attr (u | *) (Set (Attr u a)), Attr (u | *) a -> Attr * (Set (Attr u a)) + add_type(Symbol::SET_INSERT, { + let mut store = IDStore::new(); + + let u = store.fresh(); + let a = store.fresh(); + let star1 = store.fresh(); + let star2 = store.fresh(); + let star3 = store.fresh(); + + unique_function( + vec![ + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + disjunction(star1, vec![u]), + SolvedType::Apply(Symbol::SET_SET, vec![attr_type(u, a)]), + ], + ), + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![disjunction(star2, vec![u]), flex(a)], + ), + ], + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + flex(star3), + SolvedType::Apply(Symbol::SET_SET, vec![attr_type(u, a)]), + ], + ), + ) + }); + + // we can remove a key that is shared from a set of unique keys + // remove : Attr (u | *) (Set (Attr u a)), Attr * a -> Attr * (Set (Attr u a)) + add_type(Symbol::SET_REMOVE, { + let mut store = IDStore::new(); + + let u = store.fresh(); + let a = store.fresh(); + let star1 = store.fresh(); + let star2 = store.fresh(); + let star3 = store.fresh(); + + unique_function( + vec![ + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + disjunction(star1, vec![u]), + SolvedType::Apply(Symbol::SET_SET, vec![attr_type(u, a)]), + ], + ), + SolvedType::Apply(Symbol::ATTR_ATTR, vec![flex(star2), flex(a)]), + ], + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + flex(star3), + SolvedType::Apply(Symbol::SET_SET, vec![attr_type(u, a)]), + ], + ), + ) + }); + // Str module // isEmpty : Attr u Str -> Attr v Bool @@ -688,3 +1006,22 @@ fn list_type(u: VarId, a: VarId) -> SolvedType { vec![flex(u), SolvedType::Apply(Symbol::LIST_LIST, vec![flex(a)])], ) } + +#[inline(always)] +fn set_type(u: VarId, a: VarId) -> SolvedType { + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![flex(u), SolvedType::Apply(Symbol::SET_SET, vec![flex(a)])], + ) +} + +#[inline(always)] +fn map_type(u: VarId, key: VarId, value: VarId) -> SolvedType { + SolvedType::Apply( + Symbol::ATTR_ATTR, + vec![ + flex(u), + SolvedType::Apply(Symbol::MAP_MAP, vec![flex(key), flex(value)]), + ], + ) +} diff --git a/compiler/can/src/annotation.rs b/compiler/can/src/annotation.rs index 047274226b..c70e90fcd6 100644 --- a/compiler/can/src/annotation.rs +++ b/compiler/can/src/annotation.rs @@ -1,6 +1,6 @@ use crate::env::Env; use crate::scope::Scope; -use roc_collections::all::{ImMap, MutMap, MutSet, SendMap}; +use roc_collections::all::{MutSet, SendMap}; use roc_module::ident::Ident; use roc_module::ident::{Lowercase, TagName}; use roc_module::symbol::Symbol; @@ -13,19 +13,13 @@ use roc_types::types::{Alias, Problem, Type}; #[derive(Clone, Debug, PartialEq)] pub struct Annotation { pub typ: Type, - pub ftv: MutMap, - pub rigids: ImMap, + pub introduced_variables: IntroducedVariables, pub references: MutSet, pub aliases: SendMap, } -pub fn canonicalize_annotation( - env: &mut Env, - scope: &mut Scope, - annotation: &roc_parse::ast::TypeAnnotation, - region: Region, - var_store: &VarStore, -) -> Annotation { +#[derive(Clone, Debug, PartialEq, Default)] +pub struct IntroducedVariables { // NOTE on rigids // // Rigids must be unique within a type annoation. @@ -36,7 +30,44 @@ pub fn canonicalize_annotation( // But then between annotations, the same name can occur multiple times, // but a variable can only have one name. Therefore // `ftv : SendMap`. - let mut rigids = ImMap::default(); + pub wildcards: Vec, + pub var_by_name: SendMap, + pub name_by_var: SendMap, +} + +impl IntroducedVariables { + pub fn insert_named(&mut self, name: Lowercase, var: Variable) { + self.var_by_name.insert(name.clone(), var); + self.name_by_var.insert(var, name); + } + + pub fn insert_wildcard(&mut self, var: Variable) { + self.wildcards.push(var); + } + + pub fn union(&mut self, other: &Self) { + self.wildcards.extend(other.wildcards.iter().cloned()); + self.var_by_name.extend(other.var_by_name.clone()); + self.name_by_var.extend(other.name_by_var.clone()); + } + + pub fn var_by_name(&self, name: &Lowercase) -> Option<&Variable> { + self.var_by_name.get(name) + } + + pub fn name_by_var(&self, var: Variable) -> Option<&Lowercase> { + self.name_by_var.get(&var) + } +} + +pub fn canonicalize_annotation( + env: &mut Env, + scope: &mut Scope, + annotation: &roc_parse::ast::TypeAnnotation, + region: Region, + var_store: &VarStore, +) -> Annotation { + let mut introduced_variables = IntroducedVariables::default(); let mut aliases = SendMap::default(); let mut references = MutSet::default(); let typ = can_annotation_help( @@ -45,22 +76,15 @@ pub fn canonicalize_annotation( region, scope, var_store, - &mut rigids, + &mut introduced_variables, &mut aliases, &mut references, ); - let mut ftv = MutMap::default(); - - for (k, v) in rigids.clone() { - ftv.insert(v, k); - } - Annotation { typ, - ftv, + introduced_variables, references, - rigids, aliases, } } @@ -72,7 +96,7 @@ fn can_annotation_help( region: Region, scope: &mut Scope, var_store: &VarStore, - rigids: &mut ImMap, + introduced_variables: &mut IntroducedVariables, local_aliases: &mut SendMap, references: &mut MutSet, ) -> Type { @@ -89,7 +113,7 @@ fn can_annotation_help( region, scope, var_store, - rigids, + introduced_variables, local_aliases, references, ); @@ -103,7 +127,7 @@ fn can_annotation_help( region, scope, var_store, - rigids, + introduced_variables, local_aliases, references, ); @@ -148,7 +172,7 @@ fn can_annotation_help( region, scope, var_store, - rigids, + introduced_variables, local_aliases, references, ); @@ -161,12 +185,12 @@ fn can_annotation_help( BoundVariable(v) => { let name = Lowercase::from(*v); - match rigids.get(&name) { + match introduced_variables.var_by_name(&name) { Some(var) => Type::Variable(*var), None => { let var = var_store.fresh(); - rigids.insert(name, var); + introduced_variables.insert_named(name, var); Type::Variable(var) } @@ -200,7 +224,7 @@ fn can_annotation_help( region, scope, var_store, - rigids, + introduced_variables, local_aliases, references, ); @@ -214,12 +238,12 @@ fn can_annotation_help( BoundVariable(ident) => { let var_name = Lowercase::from(ident); - if let Some(var) = rigids.get(&var_name) { + if let Some(var) = introduced_variables.var_by_name(&var_name) { vars.push((var_name, Type::Variable(*var))); } else { let var = var_store.fresh(); - rigids.insert(var_name.clone(), var); + introduced_variables.insert_named(var_name.clone(), var); vars.push((var_name.clone(), Type::Variable(var))); lowercase_vars.push(Located::at(loc_var.region, (var_name, var))); @@ -276,7 +300,7 @@ fn can_annotation_help( region, scope, var_store, - rigids, + introduced_variables, local_aliases, &mut field_types, references, @@ -290,7 +314,7 @@ fn can_annotation_help( region, scope, var_store, - rigids, + introduced_variables, local_aliases, references, ), @@ -309,7 +333,7 @@ fn can_annotation_help( region, scope, var_store, - rigids, + introduced_variables, local_aliases, &mut tag_types, references, @@ -323,7 +347,7 @@ fn can_annotation_help( region, scope, var_store, - rigids, + introduced_variables, local_aliases, references, ), @@ -338,13 +362,15 @@ fn can_annotation_help( region, scope, var_store, - rigids, + introduced_variables, local_aliases, references, ), Wildcard | Malformed(_) => { let var = var_store.fresh(); + introduced_variables.insert_wildcard(var); + Type::Variable(var) } } @@ -358,7 +384,7 @@ fn can_assigned_field<'a>( region: Region, scope: &mut Scope, var_store: &VarStore, - rigids: &mut ImMap, + introduced_variables: &mut IntroducedVariables, local_aliases: &mut SendMap, field_types: &mut SendMap, references: &mut MutSet, @@ -373,7 +399,7 @@ fn can_assigned_field<'a>( region, scope, var_store, - rigids, + introduced_variables, local_aliases, references, ); @@ -385,11 +411,11 @@ fn can_assigned_field<'a>( // Interpret { a, b } as { a : a, b : b } let field_name = Lowercase::from(loc_field_name.value); let field_type = { - if let Some(var) = rigids.get(&field_name) { + if let Some(var) = introduced_variables.var_by_name(&field_name) { Type::Variable(*var) } else { let field_var = var_store.fresh(); - rigids.insert(field_name.clone(), field_var); + introduced_variables.insert_named(field_name.clone(), field_var); Type::Variable(field_var) } }; @@ -402,7 +428,7 @@ fn can_assigned_field<'a>( region, scope, var_store, - rigids, + introduced_variables, local_aliases, field_types, references, @@ -419,7 +445,7 @@ fn can_tag<'a>( region: Region, scope: &mut Scope, var_store: &VarStore, - rigids: &mut ImMap, + introduced_variables: &mut IntroducedVariables, local_aliases: &mut SendMap, tag_types: &mut Vec<(TagName, Vec)>, references: &mut MutSet, @@ -436,7 +462,7 @@ fn can_tag<'a>( region, scope, var_store, - rigids, + introduced_variables, local_aliases, references, ); @@ -458,7 +484,7 @@ fn can_tag<'a>( region, scope, var_store, - rigids, + introduced_variables, local_aliases, references, ); @@ -474,7 +500,7 @@ fn can_tag<'a>( region, scope, var_store, - rigids, + introduced_variables, local_aliases, tag_types, references, diff --git a/compiler/can/src/def.rs b/compiler/can/src/def.rs index 1e89b1591f..86511b947f 100644 --- a/compiler/can/src/def.rs +++ b/compiler/can/src/def.rs @@ -1,4 +1,5 @@ use crate::annotation::canonicalize_annotation; +use crate::annotation::IntroducedVariables; use crate::env::Env; use crate::expr::Expr::{self, *}; use crate::expr::{ @@ -28,7 +29,7 @@ pub struct Def { pub loc_expr: Located, pub expr_var: Variable, pub pattern_vars: SendMap, - pub annotation: Option<(Type, SendMap, SendMap)>, + pub annotation: Option<(Type, IntroducedVariables, SendMap)>, } #[derive(Debug)] @@ -192,7 +193,10 @@ pub fn canonicalize_defs<'a>( Vec::with_capacity(vars.len()); for loc_lowercase in vars { - if let Some(var) = can_ann.rigids.get(&loc_lowercase.value) { + if let Some(var) = can_ann + .introduced_variables + .var_by_name(&loc_lowercase.value) + { // This is a valid lowercase rigid var for the alias. can_vars.push(Located { value: (loc_lowercase.value.clone(), *var), @@ -713,14 +717,7 @@ fn canonicalize_pending_def<'a>( aliases.insert(symbol, alias); } - // union seen rigids with already found ones - for (k, v) in ann.rigids { - output.rigids.insert(k, v); - } - - for (k, v) in ann.ftv { - output.ftv.insert(k, v); - } + output.introduced_variables.union(&ann.introduced_variables); pattern_to_vars_by_symbol(&mut vars_by_symbol, &loc_can_pattern.value, expr_var); @@ -790,7 +787,11 @@ fn canonicalize_pending_def<'a>( value: loc_can_expr.value.clone(), }, pattern_vars: im::HashMap::clone(&vars_by_symbol), - annotation: Some((typ.clone(), output.rigids.clone(), ann.aliases.clone())), + annotation: Some(( + typ.clone(), + output.introduced_variables.clone(), + ann.aliases.clone(), + )), }, ); } @@ -810,7 +811,10 @@ fn canonicalize_pending_def<'a>( let mut can_vars: Vec> = Vec::with_capacity(vars.len()); for loc_lowercase in vars { - if let Some(var) = can_ann.rigids.get(&loc_lowercase.value) { + if let Some(var) = can_ann + .introduced_variables + .var_by_name(&loc_lowercase.value) + { // This is a valid lowercase rigid var for the alias. can_vars.push(Located { value: (loc_lowercase.value.clone(), *var), @@ -840,15 +844,9 @@ fn canonicalize_pending_def<'a>( let alias = scope.lookup_alias(symbol).expect("alias was not added"); aliases.insert(symbol, alias.clone()); - // aliases cannot introduce new rigids that are visible in other annotations - // but the rigids can show up in type error messages, so still register them - for (k, v) in can_ann.rigids { - output.rigids.insert(k, v); - } - - for (k, v) in can_ann.ftv { - output.ftv.insert(k, v); - } + output + .introduced_variables + .union(&can_ann.introduced_variables); } TypedBody(loc_pattern, loc_can_pattern, loc_ann, loc_expr) => { let ann = @@ -867,14 +865,7 @@ fn canonicalize_pending_def<'a>( aliases.insert(symbol, alias); } - // union seen rigids with already found ones - for (k, v) in ann.rigids { - output.rigids.insert(k, v); - } - - for (k, v) in ann.ftv { - output.ftv.insert(k, v); - } + output.introduced_variables.union(&ann.introduced_variables); // bookkeeping for tail-call detection. If we're assigning to an // identifier (e.g. `f = \x -> ...`), then this symbol can be tail-called. @@ -991,7 +982,11 @@ fn canonicalize_pending_def<'a>( value: loc_can_expr.value.clone(), }, pattern_vars: im::HashMap::clone(&vars_by_symbol), - annotation: Some((typ.clone(), output.rigids.clone(), ann.aliases.clone())), + annotation: Some(( + typ.clone(), + output.introduced_variables.clone(), + ann.aliases.clone(), + )), }, ); } @@ -1157,8 +1152,9 @@ pub fn can_defs_with_return<'a>( let (ret_expr, mut output) = canonicalize_expr(env, var_store, &mut scope, loc_ret.region, &loc_ret.value); - output.rigids = output.rigids.union(defs_output.rigids); - output.ftv = output.ftv.union(defs_output.ftv); + output + .introduced_variables + .union(&defs_output.introduced_variables); output.references = output.references.union(defs_output.references); // Now that we've collected all the references, check to see if any of the new idents diff --git a/compiler/can/src/expr.rs b/compiler/can/src/expr.rs index 26aea89420..6e34a59aed 100644 --- a/compiler/can/src/expr.rs +++ b/compiler/can/src/expr.rs @@ -1,3 +1,4 @@ +use crate::annotation::IntroducedVariables; use crate::def::{can_defs_with_return, Def}; use crate::env::Env; use crate::num::{ @@ -25,8 +26,7 @@ use std::ops::Neg; pub struct Output { pub references: References, pub tail_call: Option, - pub rigids: SendMap, - pub ftv: SendMap, + pub introduced_variables: IntroducedVariables, pub aliases: SendMap, } diff --git a/compiler/can/src/module.rs b/compiler/can/src/module.rs index 8c4342eaa0..4b3441229f 100644 --- a/compiler/can/src/module.rs +++ b/compiler/can/src/module.rs @@ -121,7 +121,7 @@ pub fn canonicalize_module_defs<'a>( } } - for (var, lowercase) in output.ftv.clone() { + for (var, lowercase) in output.introduced_variables.name_by_var.clone() { rigid_variables.insert(var, lowercase); } diff --git a/compiler/constrain/src/expr.rs b/compiler/constrain/src/expr.rs index 4f085a5f5e..c0fd8a6263 100644 --- a/compiler/constrain/src/expr.rs +++ b/compiler/constrain/src/expr.rs @@ -1,5 +1,6 @@ use crate::builtins::{empty_list_type, float_literal, int_literal, list_type, str_type}; use crate::pattern::{constrain_pattern, PatternState}; +use roc_can::annotation::IntroducedVariables; use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::LetConstraint; use roc_can::def::{Declaration, Def}; @@ -754,7 +755,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { let mut new_rigids = Vec::new(); let expr_con = match &def.annotation { - Some((annotation, free_vars, ann_def_aliases)) => { + Some((annotation, introduced_vars, ann_def_aliases)) => { def_aliases = ann_def_aliases.clone(); let arity = annotation.arity(); @@ -763,7 +764,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { let annotation = instantiate_rigids( annotation, - &free_vars, + &introduced_vars, &mut new_rigids, &mut ftv, &def.loc_pattern, @@ -821,7 +822,7 @@ fn constrain_def(env: &Env, def: &Def, body_con: Constraint) -> Constraint { fn instantiate_rigids( annotation: &Type, - free_vars: &SendMap, + introduced_vars: &IntroducedVariables, new_rigids: &mut Vec, ftv: &mut ImMap, loc_pattern: &Located, @@ -830,8 +831,8 @@ fn instantiate_rigids( let mut annotation = annotation.clone(); let mut rigid_substitution: ImMap = ImMap::default(); - for (name, var) in free_vars { - if let Some(existing_rigid) = ftv.get(name) { + for (name, var) in introduced_vars.var_by_name.iter() { + if let Some(existing_rigid) = ftv.get(&name) { rigid_substitution.insert(*var, Type::Variable(*existing_rigid)); } else { // It's possible to use this rigid in nested defs @@ -854,6 +855,8 @@ fn instantiate_rigids( } } + new_rigids.extend(introduced_vars.wildcards.iter().cloned()); + annotation } @@ -922,7 +925,7 @@ pub fn rec_defs_help( flex_info.def_types.extend(pattern_state.headers); } - Some((annotation, free_vars, ann_def_aliases)) => { + Some((annotation, introduced_vars, ann_def_aliases)) => { for (symbol, alias) in ann_def_aliases.clone() { def_aliases.insert(symbol, alias); } @@ -932,7 +935,7 @@ pub fn rec_defs_help( let annotation = instantiate_rigids( annotation, - &free_vars, + &introduced_vars, &mut new_rigids, &mut ftv, &def.loc_pattern, diff --git a/compiler/constrain/src/module.rs b/compiler/constrain/src/module.rs index 7456ac7fb9..50f49c90d6 100644 --- a/compiler/constrain/src/module.rs +++ b/compiler/constrain/src/module.rs @@ -72,6 +72,10 @@ pub fn constrain_imported_values( rigid_vars.push(var); } + for var in free_vars.wildcards { + rigid_vars.push(var); + } + // Variables can lose their name during type inference. But the unnamed // variables are still part of a signature, and thus must be treated as rigids here! for (_, var) in free_vars.unnamed_vars { @@ -151,9 +155,10 @@ pub fn load_builtin_aliases( pub struct FreeVars { pub named_vars: ImMap, pub unnamed_vars: ImMap, + pub wildcards: Vec, } -pub fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &VarStore) -> Type { +fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &VarStore) -> Type { use roc_types::solved_types::SolvedType::*; match solved_type { @@ -196,7 +201,11 @@ pub fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &V Type::Variable(var) } } - Wildcard => Type::Variable(var_store.fresh()), + Wildcard => { + let var = var_store.fresh(); + free_vars.wildcards.push(var); + Type::Variable(var) + } Record { fields, ext } => { let mut new_fields = SendMap::default(); diff --git a/compiler/constrain/src/uniqueness.rs b/compiler/constrain/src/uniqueness.rs index 0d4b1d621f..dda91d4360 100644 --- a/compiler/constrain/src/uniqueness.rs +++ b/compiler/constrain/src/uniqueness.rs @@ -1,4 +1,5 @@ use crate::expr::{exists, exists_with_aliases, Info}; +use roc_can::annotation::IntroducedVariables; use roc_can::constraint::Constraint::{self, *}; use roc_can::constraint::LetConstraint; use roc_can::def::{Declaration, Def}; @@ -59,7 +60,7 @@ pub fn constrain_declaration( pub fn constrain_decls( home: ModuleId, decls: &[Declaration], - aliases: SendMap, + mut aliases: SendMap, var_store: &VarStore, ) -> Constraint { let mut constraint = Constraint::SaveTheEnvironment; @@ -81,6 +82,8 @@ pub fn constrain_decls( } } + aliases_to_attr_type(var_store, &mut aliases); + for decl in decls.iter().rev() { // NOTE: rigids are empty because they are not shared between top-level definitions match decl { @@ -1635,14 +1638,15 @@ fn constrain_def( let mut new_rigids = Vec::new(); let expr_con = match &def.annotation { - Some((annotation, free_vars, ann_def_aliases)) => { + Some((annotation, introduced_vars, ann_def_aliases)) => { def_aliases = ann_def_aliases.clone(); let arity = annotation.arity(); let mut ftv = env.rigids.clone(); + let annotation = instantiate_rigids( var_store, annotation, - &free_vars, + &introduced_vars, &mut new_rigids, &mut ftv, &def.loc_pattern, @@ -1709,7 +1713,7 @@ fn constrain_def( fn instantiate_rigids( var_store: &VarStore, annotation: &Type, - free_vars: &SendMap, + introduced_vars: &IntroducedVariables, new_rigids: &mut Vec, ftv: &mut ImMap, loc_pattern: &Located, @@ -1720,7 +1724,7 @@ fn instantiate_rigids( let mut rigid_substitution: ImMap = ImMap::default(); - for (name, var) in free_vars { + for (name, var) in introduced_vars.var_by_name.iter() { if let Some((existing_rigid, existing_uvar)) = ftv.get(&name) { rigid_substitution.insert( *var, @@ -1764,6 +1768,7 @@ fn instantiate_rigids( } new_rigids.extend(uniq_vars); + new_rigids.extend(introduced_vars.wildcards.iter().cloned()); for (_, v) in new_rigid_pairs { new_rigids.push(v); @@ -1854,7 +1859,7 @@ pub fn rec_defs_help( flex_info.def_types.extend(pattern_state.headers); } - Some((annotation, free_vars, ann_def_aliases)) => { + Some((annotation, introduced_vars, ann_def_aliases)) => { for (symbol, alias) in ann_def_aliases.clone() { def_aliases.insert(symbol, alias); } @@ -1863,7 +1868,7 @@ pub fn rec_defs_help( let annotation = instantiate_rigids( var_store, annotation, - &free_vars, + &introduced_vars, &mut new_rigids, &mut ftv, &def.loc_pattern, diff --git a/compiler/tests/fixtures/build/interface_with_deps/AStar.roc b/compiler/tests/fixtures/build/interface_with_deps/AStar.roc index c48c171844..c03a88a7c5 100644 --- a/compiler/tests/fixtures/build/interface_with_deps/AStar.roc +++ b/compiler/tests/fixtures/build/interface_with_deps/AStar.roc @@ -5,11 +5,11 @@ interface AStar # a port of https://github.com/krisajenkins/elm-astar/blob/2.1.3/src/AStar/Generalised.elm -Model xyz : - { evaluated : Set xyz - , openSet : Set xyz - , costs : Map.Map xyz Float - , cameFrom : Map.Map xyz xyz +Model position : + { evaluated : Set position + , openSet : Set position + , costs : Map.Map position Float + , cameFrom : Map.Map position position } diff --git a/compiler/tests/helpers/mod.rs b/compiler/tests/helpers/mod.rs index 81363ef596..540724ac48 100644 --- a/compiler/tests/helpers/mod.rs +++ b/compiler/tests/helpers/mod.rs @@ -47,6 +47,31 @@ pub fn infer_expr( (content, solved.into_inner()) } +/// Used in the with_larger_debug_stack() function, for tests that otherwise +/// run out of stack space in debug builds (but don't in --release builds) +#[allow(dead_code)] +const EXPANDED_STACK_SIZE: usize = 4 * 1024 * 1024; + +/// Without this, some tests pass in `cargo test --release` but fail without +/// the --release flag because they run out of stack space. This increases +/// stack size for debug builds only, while leaving the stack space at the default +/// amount for release builds. +#[allow(dead_code)] +#[cfg(debug_assertions)] +pub fn with_larger_debug_stack(run_test: F) +where + F: FnOnce() -> (), + F: Send, + F: 'static, +{ + std::thread::Builder::new() + .stack_size(EXPANDED_STACK_SIZE) + .spawn(run_test) + .expect("Error while spawning expanded dev stack size thread") + .join() + .expect("Error while joining expanded dev stack size thread") +} + /// In --release builds, don't increase the stack size. Run the test normally. /// This way, we find out if any of our tests are blowing the stack even after /// optimizations in release builds. diff --git a/compiler/tests/test_infer.rs b/compiler/tests/test_infer.rs index 2b61b95c96..17031d481a 100644 --- a/compiler/tests/test_infer.rs +++ b/compiler/tests/test_infer.rs @@ -37,7 +37,7 @@ mod test_infer { assert_correct_variable_usage(&constraint); - for (var, name) in output.ftv { + for (var, name) in output.introduced_variables.name_by_var { subs.rigid_var(var, name); } diff --git a/compiler/tests/test_uniqueness_infer.rs b/compiler/tests/test_uniqueness_infer.rs index f520676c6a..66edb43da8 100644 --- a/compiler/tests/test_uniqueness_infer.rs +++ b/compiler/tests/test_uniqueness_infer.rs @@ -10,7 +10,9 @@ mod helpers; #[cfg(test)] mod test_infer_uniq { - use crate::helpers::{assert_correct_variable_usage, infer_expr, uniq_expr}; + use crate::helpers::{ + assert_correct_variable_usage, infer_expr, uniq_expr, with_larger_debug_stack, + }; use roc_types::pretty_print::{content_to_string, name_all_type_vars}; // HELPERS @@ -21,7 +23,7 @@ mod test_infer_uniq { assert_correct_variable_usage(&constraint); - for (var, name) in output.ftv { + for (var, name) in output.introduced_variables.name_by_var { subs.rigid_var(var, name); } @@ -2082,7 +2084,7 @@ mod test_infer_uniq { reverse "# ), - "Attr * (Attr * (List (Attr (a | b) c)) -> Attr (* | a | b) (List (Attr a c)))", + "Attr * (Attr * (List (Attr (a | b) c)) -> Attr (* | a | b) (List (Attr b c)))", ); } @@ -2099,7 +2101,7 @@ mod test_infer_uniq { } #[test] - fn update_cost() { + fn use_correct_ext_var() { infer_eq( indoc!( r#" @@ -2115,4 +2117,250 @@ mod test_infer_uniq { "Attr * (Attr (* | a | b) { p : (Attr b *), q : (Attr a *) }* -> Attr * Int)", ); } + + #[test] + fn reconstruct_path() { + infer_eq( + indoc!( + r#" + reconstructPath : Map position position, position -> List position + reconstructPath = \cameFrom, goal -> + when Map.get cameFrom goal is + Err KeyNotFound -> + [] + + Ok next -> + List.push (reconstructPath cameFrom next) goal + + reconstructPath + "# + ), + "Attr Shared (Attr Shared (Map (Attr Shared position) (Attr Shared position)), Attr Shared position -> Attr * (List (Attr Shared position)))" + ); + } + + #[test] + fn cheapest_open() { + infer_eq( + indoc!( + r#" + Model position : { evaluated : Set position + , openSet : Set position + , costs : Map.Map position Float + , cameFrom : Map.Map position position + } + + cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]* + cheapestOpen = \costFunction, model -> + + folder = \position, resSmallestSoFar -> + when Map.get model.costs position is + Err e -> + Err e + + Ok cost -> + positionCost = costFunction position + + when resSmallestSoFar is + Err _ -> Ok { position, cost: cost + positionCost } + Ok smallestSoFar -> + if positionCost + cost < smallestSoFar.cost then + Ok { position, cost: cost + positionCost } + + else + Ok smallestSoFar + + Set.foldl model.openSet folder (Err KeyNotFound) + |> Result.map (\x -> x.position) + + cheapestOpen + "# + ), + "Attr * (Attr * (Attr Shared position -> Attr Shared Float), Attr * (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))" + ); + } + + #[test] + fn update_cost() { + infer_eq( + indoc!( + r#" + Model position : { evaluated : Set position + , openSet : Set position + , costs : Map.Map position Float + , cameFrom : Map.Map position position + } + + reconstructPath : Map position position, position -> List position + reconstructPath = \cameFrom, goal -> + when Map.get cameFrom goal is + Err KeyNotFound -> + [] + + Ok next -> + List.push (reconstructPath cameFrom next) goal + + updateCost : position, position, Model position -> Model position + updateCost = \current, neighbour, model -> + newCameFrom = Map.insert model.cameFrom neighbour current + + newCosts = Map.insert model.costs neighbour distanceTo + + distanceTo = reconstructPath newCameFrom neighbour + |> List.length + |> Num.toFloat + + newModel = { model & costs : newCosts , cameFrom : newCameFrom } + + when Map.get model.costs neighbour is + Err KeyNotFound -> + newModel + + Ok previousDistance -> + if distanceTo < previousDistance then + newModel + + else + model + updateCost + "# + ), + "Attr * (Attr Shared position, Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr Shared (Model (Attr Shared position)))" + ); + } + + #[test] + fn astar_full_code() { + with_larger_debug_stack(|| { + infer_eq( + indoc!( + r#" + Model position : { evaluated : Set position + , openSet : Set position + , costs : Map.Map position Float + , cameFrom : Map.Map position position + } + + + initialModel : position -> Model position + initialModel = \start -> + { evaluated : Set.empty + , openSet : Set.singleton start + , costs : Map.singleton start 0.0 + , cameFrom : Map.empty + } + + + cheapestOpen : (position -> Float), Model position -> Result position [ KeyNotFound ]* + cheapestOpen = \costFunction, model -> + + folder = \position, resSmallestSoFar -> + when Map.get model.costs position is + Err e -> + Err e + + Ok cost -> + positionCost = costFunction position + + when resSmallestSoFar is + Err _ -> Ok { position, cost: cost + positionCost } + Ok smallestSoFar -> + if positionCost + cost < smallestSoFar.cost then + Ok { position, cost: cost + positionCost } + + else + Ok smallestSoFar + + Set.foldl model.openSet folder (Err KeyNotFound) + |> Result.map (\x -> x.position) + + + + reconstructPath : Map position position, position -> List position + reconstructPath = \cameFrom, goal -> + when Map.get cameFrom goal is + Err KeyNotFound -> + [] + + Ok next -> + List.push (reconstructPath cameFrom next) goal + + updateCost : position, position, Model position -> Model position + updateCost = \current, neighbour, model -> + newCameFrom = Map.insert model.cameFrom neighbour current + + newCosts = Map.insert model.costs neighbour distanceTo + + distanceTo = reconstructPath newCameFrom neighbour + |> List.length + |> Num.toFloat + + newModel = { model & costs : newCosts , cameFrom : newCameFrom } + + when Map.get model.costs neighbour is + Err KeyNotFound -> + newModel + + Ok previousDistance -> + if distanceTo < previousDistance then + newModel + + else + model + + + findPath : { costFunction: (position, position -> Float), moveFunction: (position -> Set position), start : position, end : position } -> Result (List position) [ KeyNotFound ]* + findPath = \{ costFunction, moveFunction, start, end } -> + astar costFunction moveFunction end (initialModel start) + + + astar : (position, position -> Float), (position -> Set position), position, Model position -> [ Err [ KeyNotFound ]*, Ok (List position) ]* + astar = \costFn, moveFn, goal, model -> + when cheapestOpen (\position -> costFn goal position) model is + Err _ -> + Err KeyNotFound + + Ok current -> + if current == goal then + Ok (reconstructPath model.cameFrom goal) + + else + + modelPopped = { model & openSet : Set.remove model.openSet current, evaluated : Set.insert model.evaluated current } + + neighbours = moveFn current + + newNeighbours = Set.diff neighbours modelPopped.evaluated + + modelWithNeighbours = { modelPopped & openSet : Set.union modelPopped.openSet newNeighbours } + + modelWithCosts = Set.foldl newNeighbours (\nb, md -> updateCost current nb md) modelWithNeighbours + + astar costFn moveFn goal modelWithCosts + + 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 ]*)))" + ) + }); + } + + #[test] + fn instantiated_alias() { + infer_eq( + indoc!( + r#" + Model a : { foo : Set a } + + + initialModel : position -> Model Int + initialModel = \_ -> { foo : Set.empty } + + initialModel + "# + ), + "Attr * (Attr * position -> Attr * (Model (Attr * Int)))", + ); + } } diff --git a/compiler/tests/test_uniqueness_load.rs b/compiler/tests/test_uniqueness_load.rs index d32c07f5dd..7feb688d80 100644 --- a/compiler/tests/test_uniqueness_load.rs +++ b/compiler/tests/test_uniqueness_load.rs @@ -222,6 +222,26 @@ mod test_uniqueness_load { }); } + #[test] + fn load_astar() { + test_async(async { + let subs_by_module = MutMap::default(); + let loaded_module = load_fixture("interface_with_deps", "AStar", subs_by_module).await; + + expect_types( + loaded_module, + hashmap! { + "findPath" => "Attr * (Attr * { costFunction : (Attr Shared (Attr Shared position, Attr Shared position -> Attr Shared Float)), end : (Attr Shared position), moveFunction : (Attr Shared (Attr Shared position -> Attr * (Set (Attr Shared position)))), start : (Attr Shared position) } -> Attr * (Result (Attr * (List (Attr Shared position))) (Attr * [ KeyNotFound ]*)))", + "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)))", + "updateCost" => "Attr * (Attr Shared position, Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr Shared (Model (Attr Shared position)))", + "cheapestOpen" => "Attr * (Attr * (Attr Shared position -> Attr Shared Float), Attr * (Model (Attr Shared position)) -> Attr * (Result (Attr Shared position) (Attr * [ KeyNotFound ]*)))", + "astar" => "Attr Shared (Attr Shared (Attr Shared position, Attr Shared position -> Attr Shared Float), Attr Shared (Attr Shared position -> Attr * (Set (Attr Shared position))), Attr Shared position, Attr Shared (Model (Attr Shared position)) -> Attr * [ Err (Attr * [ KeyNotFound ]*), Ok (Attr * (List (Attr Shared position))) ]*)", + }, + ); + }); + } + #[test] fn load_and_typecheck_quicksort() { test_async(async {