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}; use roc_can::expected::{Expected, PExpected}; use roc_can::expr::{Expr, Field, WhenBranch}; use roc_can::pattern::{Pattern, RecordDestruct}; use roc_collections::all::{ImMap, ImSet, Index, SendMap}; use roc_module::ident::{Ident, Lowercase}; use roc_module::symbol::{ModuleId, Symbol}; use roc_region::all::{Located, Region}; use roc_types::boolean_algebra::Bool; use roc_types::subs::{VarStore, Variable}; use roc_types::types::AnnotationSource::{self, *}; use roc_types::types::Type::{self, *}; use roc_types::types::{Alias, Category, PReason, Reason}; use roc_uniq::builtins::{attr_type, empty_list_type, list_type, str_type}; use roc_uniq::sharing::{self, Container, FieldAccess, Mark, Usage, VarUsage}; pub struct Env { /// Whenever we encounter a user-defined type variable (a "rigid" var for short), /// for example `u` and `a` in the annotation `identity : Attr u a -> Attr u a`, we add it to this /// map so that expressions within that annotation can share these vars. pub rigids: ImMap, pub home: ModuleId, } pub fn constrain_declaration( home: ModuleId, var_store: &mut VarStore, region: Region, loc_expr: &Located, _declared_idents: &ImMap, expected: Expected, ) -> Constraint { // TODO this means usage is local to individual declarations. // Should be per-module in the future! let mut var_usage = VarUsage::default(); sharing::annotate_usage(&loc_expr.value, &mut var_usage); let mut applied_usage_constraint = ImSet::default(); constrain_expr( &Env { rigids: ImMap::default(), home, }, var_store, &var_usage, &mut applied_usage_constraint, region, &loc_expr.value, expected, ) } /// Constrain top-level module declarations #[inline(always)] pub fn constrain_decls( home: ModuleId, decls: &[Declaration], mut aliases: SendMap, var_store: &mut VarStore, ) -> Constraint { let mut constraint = Constraint::SaveTheEnvironment; // perform usage analysis on the whole file let mut var_usage = VarUsage::default(); for decl in decls.iter().rev() { // NOTE: rigids are empty because they are not shared between top-level definitions match decl { Declaration::Declare(def) | Declaration::Builtin(def) => { sharing::annotate_usage(&def.loc_expr.value, &mut var_usage); } Declaration::DeclareRec(defs) => { for def in defs { sharing::annotate_usage(&def.loc_expr.value, &mut var_usage); } } Declaration::InvalidCycle(_, _) => { // any usage of a value defined in an invalid cycle will blow up // so for the analysis usage by such values doesn't count continue; } } } aliases_to_attr_type(var_store, &mut aliases); let mut env = Env { home, rigids: ImMap::default(), }; for decl in decls.iter().rev() { // clear the set of rigids from the previous iteration. // rigids are not shared between top-level definitions. env.rigids.clear(); match decl { Declaration::Declare(def) | Declaration::Builtin(def) => { constraint = exists_with_aliases( aliases.clone(), Vec::new(), constrain_def( &env, var_store, &var_usage, &mut ImSet::default(), def, constraint, ), ); } Declaration::DeclareRec(defs) => { constraint = exists_with_aliases( aliases.clone(), Vec::new(), constrain_recursive_defs( &env, var_store, &var_usage, &mut ImSet::default(), defs, constraint, ), ); } Declaration::InvalidCycle(_, _) => { // invalid cycles give a canonicalization error. we skip them here. continue; } } } constraint } pub struct PatternState { pub headers: SendMap>, pub vars: Vec, pub constraints: Vec, } fn constrain_pattern( var_store: &mut VarStore, state: &mut PatternState, pattern: &Located, expected: PExpected, ) { use roc_can::pattern::Pattern::*; use roc_types::types::PatternCategory; let region = pattern.region; match &pattern.value { Identifier(symbol) => { state.headers.insert( *symbol, Located { region: pattern.region, value: expected.get_type(), }, ); } NumLiteral(inner_var, _) => { let (num_uvar, val_uvar, num_type, num_var) = unique_unbound_num(*inner_var, var_store); state.constraints.push(exists( vec![val_uvar, num_uvar, num_var, *inner_var], Constraint::Pattern(pattern.region, PatternCategory::Num, num_type, expected), )); } IntLiteral(_) => { let (num_uvar, int_uvar, num_type) = unique_int(var_store); state.constraints.push(exists( vec![num_uvar, int_uvar], Constraint::Pattern(pattern.region, PatternCategory::Int, num_type, expected), )); } FloatLiteral(_) => { let (num_uvar, float_uvar, num_type) = unique_float(var_store); state.constraints.push(exists( vec![num_uvar, float_uvar], Constraint::Pattern(pattern.region, PatternCategory::Float, num_type, expected), )); } StrLiteral(_) => { let uniq_var = var_store.fresh(); state.constraints.push(exists( vec![uniq_var], Constraint::Pattern( pattern.region, PatternCategory::Str, str_type(Bool::variable(uniq_var)), expected, ), )); } RecordDestructure { whole_var, ext_var, destructs, } => { // TODO if a subpattern doesn't bind any identifiers, it doesn't count for uniqueness let mut pattern_uniq_vars = Vec::with_capacity(destructs.len()); state.vars.push(*whole_var); state.vars.push(*ext_var); let ext_type = Type::Variable(*ext_var); let mut field_types: SendMap = SendMap::default(); for Located { value: RecordDestruct { var, label, symbol, guard, }, .. } in destructs { let pat_uniq_var = var_store.fresh(); pattern_uniq_vars.push(pat_uniq_var); let pat_type = attr_type(Bool::variable(pat_uniq_var), Type::Variable(*var)); let expected = PExpected::NoExpectation(pat_type.clone()); if !state.headers.contains_key(&symbol) { state .headers .insert(*symbol, Located::at(pattern.region, pat_type.clone())); } field_types.insert(label.clone(), pat_type.clone()); if let Some((guard_var, loc_guard)) = guard { state.constraints.push(Constraint::Pattern( pattern.region, PatternCategory::PatternGuard, Type::Variable(*guard_var), PExpected::NoExpectation(pat_type.clone()), )); state.vars.push(*guard_var); constrain_pattern(var_store, state, loc_guard, expected); } state.vars.push(*var); } let record_uniq_type = { let empty_var = var_store.fresh(); state.vars.push(empty_var); state.vars.extend(pattern_uniq_vars.clone()); Bool::container(empty_var, pattern_uniq_vars) }; let record_type = attr_type( record_uniq_type, Type::Record(field_types, Box::new(ext_type)), ); let whole_con = Constraint::Eq( Type::Variable(*whole_var), Expected::NoExpectation(record_type), Category::Storage, region, ); let record_con = Constraint::Pattern( region, PatternCategory::Record, Type::Variable(*whole_var), expected, ); state.constraints.push(whole_con); state.constraints.push(record_con); } AppliedTag { whole_var, ext_var, tag_name, arguments, } => { // TODO if a subpattern doesn't bind any identifiers, it doesn't count for uniqueness let mut argument_types = Vec::with_capacity(arguments.len()); let mut pattern_uniq_vars = Vec::with_capacity(arguments.len()); for (pattern_var, loc_pattern) in arguments { state.vars.push(*pattern_var); let pat_uniq_var = var_store.fresh(); pattern_uniq_vars.push(pat_uniq_var); let pattern_type = attr_type(Bool::variable(pat_uniq_var), Type::Variable(*pattern_var)); argument_types.push(pattern_type.clone()); let expected = PExpected::NoExpectation(pattern_type); constrain_pattern(var_store, state, loc_pattern, expected); } let tag_union_uniq_type = { let empty_var = var_store.fresh(); state.vars.push(empty_var); state.vars.extend(pattern_uniq_vars.clone()); Bool::container(empty_var, pattern_uniq_vars) }; let union_type = attr_type( tag_union_uniq_type, Type::TagUnion( vec![(tag_name.clone(), argument_types)], Box::new(Type::Variable(*ext_var)), ), ); let whole_con = Constraint::Eq( Type::Variable(*whole_var), Expected::NoExpectation(union_type), Category::Storage, region, ); let tag_con = Constraint::Pattern( region, PatternCategory::Ctor(tag_name.clone()), Type::Variable(*whole_var), expected, ); state.vars.push(*whole_var); state.vars.push(*ext_var); state.constraints.push(whole_con); state.constraints.push(tag_con); } Underscore | Shadowed(_, _) | MalformedPattern(_, _) | UnsupportedPattern(_) => { // no constraints } } } fn unique_unbound_num( inner_var: Variable, var_store: &mut VarStore, ) -> (Variable, Variable, Type, Variable) { let num_var = var_store.fresh(); let num_uvar = var_store.fresh(); let val_uvar = var_store.fresh(); let val_type = Type::Variable(inner_var); let val_utype = attr_type(Bool::variable(val_uvar), val_type); let num_utype = Type::Apply(Symbol::NUM_NUM, vec![val_utype]); let num_type = attr_type(Bool::variable(num_uvar), num_utype); (num_uvar, val_uvar, num_type, num_var) } fn unique_num(var_store: &mut VarStore, symbol: Symbol) -> (Variable, Variable, Type) { let num_uvar = var_store.fresh(); let val_uvar = var_store.fresh(); let val_type = Type::Apply(symbol, Vec::new()); let val_utype = attr_type(Bool::variable(val_uvar), val_type); let num_utype = Type::Apply(Symbol::NUM_NUM, vec![val_utype]); let num_type = attr_type(Bool::variable(num_uvar), num_utype); (num_uvar, val_uvar, num_type) } fn unique_int(var_store: &mut VarStore) -> (Variable, Variable, Type) { unique_num(var_store, Symbol::NUM_INTEGER) } fn unique_float(var_store: &mut VarStore) -> (Variable, Variable, Type) { unique_num(var_store, Symbol::NUM_FLOATINGPOINT) } pub fn constrain_expr( env: &Env, var_store: &mut VarStore, var_usage: &VarUsage, applied_usage_constraint: &mut ImSet, region: Region, expr: &Expr, expected: Expected, ) -> Constraint { pub use roc_can::expr::Expr::*; match expr { Num(inner_var, _) => { let var = var_store.fresh(); let (num_uvar, val_uvar, num_type, num_var) = unique_unbound_num(*inner_var, var_store); exists( vec![var, *inner_var, val_uvar, num_uvar, num_var], And(vec![ Eq( Type::Variable(var), Expected::ForReason(Reason::NumLiteral, num_type, region), Category::Num, region, ), Eq(Type::Variable(var), expected, Category::Num, region), ]), ) } Int(var, _) => { let (num_uvar, int_uvar, num_type) = unique_int(var_store); exists( vec![*var, num_uvar, int_uvar], And(vec![ Eq( Type::Variable(*var), Expected::ForReason(Reason::IntLiteral, num_type, region), Category::Int, region, ), Eq(Type::Variable(*var), expected, Category::Int, region), ]), ) } Float(var, _) => { let (num_uvar, float_uvar, num_type) = unique_float(var_store); exists( vec![*var, num_uvar, float_uvar], And(vec![ Eq( Type::Variable(*var), Expected::ForReason(Reason::FloatLiteral, num_type, region), Category::Float, region, ), Eq(Type::Variable(*var), expected, Category::Float, region), ]), ) } BlockStr(_) | Str(_) => { let uniq_type = var_store.fresh(); let inferred = str_type(Bool::variable(uniq_type)); exists( vec![uniq_type], Eq(inferred, expected, Category::Str, region), ) } EmptyRecord => { let uniq_type = var_store.fresh(); exists( vec![uniq_type], Eq( attr_type(Bool::variable(uniq_type), EmptyRec), expected, Category::Record, region, ), ) } Record { record_var, fields } => { // NOTE: canonicalization guarantees at least one field // zero fields generates an EmptyRecord let mut field_types = SendMap::default(); let mut field_vars = Vec::with_capacity(fields.len()); field_vars.push(*record_var); // Constraints need capacity for each field + 1 for the record itself + 1 for ext let mut constraints = Vec::with_capacity(2 + fields.len()); for (label, ref field) in fields.iter() { let field_var = var_store.fresh(); let field_type = Variable(field_var); let field_expected = Expected::NoExpectation(field_type.clone()); let loc_expr = &*field.loc_expr; let field_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_expr.region, &loc_expr.value, field_expected, ); field_vars.push(field_var); field_types.insert(label.clone(), field_type); constraints.push(field_con); } let record_uniq_var = var_store.fresh(); field_vars.push(record_uniq_var); let record_type = attr_type( Bool::variable(record_uniq_var), 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(Type::EmptyRec), ), ); let record_con = Eq(record_type, expected.clone(), Category::Record, region); let ext_con = Eq( Type::Variable(*record_var), expected, Category::Record, region, ); constraints.push(record_con); constraints.push(ext_con); exists(field_vars, And(constraints)) } Tag { variant_var, ext_var, name, arguments, } => { let mut vars = Vec::with_capacity(arguments.len()); let mut types = Vec::with_capacity(arguments.len()); let mut arg_cons = Vec::with_capacity(arguments.len()); for (var, loc_expr) in arguments { let arg_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_expr.region, &loc_expr.value, Expected::NoExpectation(Type::Variable(*var)), ); arg_cons.push(arg_con); vars.push(*var); types.push(Type::Variable(*var)); } let uniq_var = var_store.fresh(); let union_type = attr_type( Bool::variable(uniq_var), Type::TagUnion( vec![(name.clone(), types)], Box::new(Type::Variable(*ext_var)), ), ); let union_con = Eq( union_type, expected.clone(), Category::TagApply(name.clone()), region, ); let ast_con = Eq( Type::Variable(*variant_var), expected, Category::TagApply(name.clone()), region, ); vars.push(uniq_var); vars.push(*variant_var); vars.push(*ext_var); arg_cons.push(union_con); arg_cons.push(ast_con); exists(vars, And(arg_cons)) } List { elem_var, loc_elems, } => { let uniq_var = var_store.fresh(); if loc_elems.is_empty() { let inferred = empty_list_type(Bool::variable(uniq_var), *elem_var); exists( vec![*elem_var, uniq_var], Eq(inferred, expected, Category::List, region), ) } else { // constrain `expected ~ List a` and that all elements `~ a`. let entry_type = Type::Variable(*elem_var); let mut constraints = Vec::with_capacity(1 + loc_elems.len()); for (index, loc_elem) in loc_elems.iter().enumerate() { let elem_expected = Expected::ForReason( Reason::ElemInList { index: Index::zero_based(index), }, entry_type.clone(), region, ); let constraint = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_elem.region, &loc_elem.value, elem_expected, ); constraints.push(constraint); } let inferred = list_type(Bool::variable(uniq_var), entry_type); constraints.push(Eq(inferred, expected, Category::List, region)); exists(vec![*elem_var, uniq_var], And(constraints)) } } Var(symbol) => { let usage = var_usage.get_usage(*symbol); constrain_var( var_store, applied_usage_constraint, *symbol, usage, region, expected, ) } Closure(fn_var, _symbol, recursion, args, boxed) => { use roc_can::expr::Recursive; let (loc_body_expr, ret_var) = &**boxed; let mut state = PatternState { headers: SendMap::default(), vars: Vec::with_capacity(args.len()), constraints: Vec::with_capacity(1), }; let mut vars = Vec::with_capacity(state.vars.capacity() + 1); let mut pattern_types = Vec::with_capacity(state.vars.capacity()); let ret_var = *ret_var; let ret_type = Type::Variable(ret_var); vars.push(ret_var); vars.push(*fn_var); for (pattern_var, loc_pattern) in args { let pattern_type = Type::Variable(*pattern_var); let pattern_expected = PExpected::NoExpectation(pattern_type.clone()); pattern_types.push(pattern_type); constrain_pattern(var_store, &mut state, loc_pattern, pattern_expected); vars.push(*pattern_var); } let fn_uniq_type; if let Recursive::NotRecursive = recursion { let fn_uniq_var = var_store.fresh(); vars.push(fn_uniq_var); fn_uniq_type = Bool::variable(fn_uniq_var); } else { // recursive definitions MUST be Shared fn_uniq_type = Bool::shared() } let fn_type = attr_type( fn_uniq_type, Type::Function(pattern_types, Box::new(ret_type.clone())), ); let body_type = Expected::NoExpectation(ret_type); let ret_constraint = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_body_expr.region, &loc_body_expr.value, body_type, ); let defs_constraint = And(state.constraints); exists( vars, And(vec![ Let(Box::new(LetConstraint { rigid_vars: Vec::new(), flex_vars: state.vars, def_types: state.headers, def_aliases: SendMap::default(), defs_constraint, ret_constraint, })), // "the closure's type is equal to expected type" Eq(fn_type.clone(), expected, Category::Lambda, region), // "fn_var is equal to the closure's type" - fn_var is used in code gen Eq( Type::Variable(*fn_var), Expected::NoExpectation(fn_type), Category::Lambda, region, ), ]), ) } Call(boxed, loc_args, _) => { let (fn_var, fn_expr, ret_var) = &**boxed; let fn_type = Variable(*fn_var); let ret_type = Variable(*ret_var); let fn_expected = Expected::NoExpectation(fn_type.clone()); let fn_region = fn_expr.region; let opt_symbol = if let Var(symbol) = fn_expr.value { Some(symbol) } else { None }; let mut vars = Vec::with_capacity(2 + loc_args.len()); vars.push(*fn_var); vars.push(*ret_var); // Canonicalize the function expression and its arguments let fn_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, fn_region, &fn_expr.value, fn_expected, ); let fn_reason = Reason::FnCall { name: opt_symbol, arity: loc_args.len() as u8, }; let mut arg_types = Vec::with_capacity(loc_args.len()); let mut arg_cons = Vec::with_capacity(loc_args.len()); for (index, (arg_var, loc_arg)) in loc_args.iter().enumerate() { let region = loc_arg.region; let arg_type = Variable(*arg_var); let reason = Reason::FnArg { name: opt_symbol, arg_index: Index::zero_based(index), }; let expected_arg = Expected::ForReason(reason, arg_type.clone(), region); let arg_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_arg.region, &loc_arg.value, expected_arg, ); vars.push(*arg_var); arg_types.push(arg_type); arg_cons.push(arg_con); } let expected_uniq_type = var_store.fresh(); vars.push(expected_uniq_type); let expected_fn_type = Expected::ForReason( fn_reason, attr_type( Bool::variable(expected_uniq_type), Function(arg_types, Box::new(ret_type.clone())), ), region, ); exists( vars, And(vec![ fn_con, Eq( fn_type, expected_fn_type, Category::CallResult(opt_symbol), fn_region, ), And(arg_cons), Eq(ret_type, expected, Category::CallResult(opt_symbol), region), ]), ) } RunLowLevel { op, args, ret_var } => { // This is a modified version of what we do for function calls. let ret_type = Variable(*ret_var); let mut vars = Vec::with_capacity(1 + args.len()); vars.push(*ret_var); // Canonicalize the function expression and its arguments let mut arg_types = Vec::with_capacity(args.len()); let mut arg_cons = Vec::with_capacity(args.len()); for (index, (arg_var, arg_expr)) in args.iter().enumerate() { let arg_type = Variable(*arg_var); let reason = Reason::LowLevelOpArg { op: *op, arg_index: Index::zero_based(index), }; let expected_arg = Expected::ForReason(reason, arg_type.clone(), region); let arg_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, Region::zero(), arg_expr, expected_arg, ); vars.push(*arg_var); arg_types.push(arg_type); arg_cons.push(arg_con); } let expected_uniq_type = var_store.fresh(); vars.push(expected_uniq_type); exists( vars, And(vec![ And(arg_cons), Eq(ret_type, expected, Category::LowLevelOpResult(*op), region), ]), ) } LetRec(defs, loc_ret, var, unlifted_aliases) => { // NOTE doesn't currently unregister bound symbols // may be a problem when symbols are not globally unique let body_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_ret.region, &loc_ret.value, expected.clone(), ); let mut aliases = unlifted_aliases.clone(); aliases_to_attr_type(var_store, &mut aliases); exists_with_aliases( aliases, vec![*var], And(vec![ constrain_recursive_defs( env, var_store, var_usage, applied_usage_constraint, defs, body_con, ), // Record the type of tne entire def-expression in the variable. // Code gen will need that later! Eq( Type::Variable(*var), expected, Category::Storage, loc_ret.region, ), ]), ) } LetNonRec(def, loc_ret, var, unlifted_aliases) => { // NOTE doesn't currently unregister bound symbols // may be a problem when symbols are not globally unique let body_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_ret.region, &loc_ret.value, expected.clone(), ); let mut aliases = unlifted_aliases.clone(); aliases_to_attr_type(var_store, &mut aliases); exists_with_aliases( aliases, vec![*var], And(vec![ constrain_def( env, var_store, var_usage, applied_usage_constraint, def, body_con, ), // Record the type of tne entire def-expression in the variable. // Code gen will need that later! Eq( Type::Variable(*var), expected, Category::Storage, loc_ret.region, ), ]), ) } If { cond_var, branch_var, branches, final_else, } => { // TODO use Bool alias here, so we don't allocate this type every time let bool_type = Type::Variable(Variable::BOOL); let mut branch_cons = Vec::with_capacity(2 * branches.len() + 2); let mut cond_uniq_vars = Vec::with_capacity(branches.len() + 2); // TODO why does this cond var exist? is it for error messages? let cond_uniq_var = var_store.fresh(); cond_uniq_vars.push(cond_uniq_var); let cond_var_is_bool_con = Eq( Type::Variable(*cond_var), Expected::ForReason( Reason::IfCondition, attr_type(Bool::variable(cond_uniq_var), bool_type.clone()), region, ), Category::If, Region::zero(), ); branch_cons.push(cond_var_is_bool_con); match expected { Expected::FromAnnotation(name, arity, _, tipe) => { for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { let cond_uniq_var = var_store.fresh(); let expect_bool = Expected::ForReason( Reason::IfCondition, attr_type(Bool::variable(cond_uniq_var), bool_type.clone()), region, ); cond_uniq_vars.push(cond_uniq_var); let cond_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_cond.region, &loc_cond.value, expect_bool, ); let then_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_body.region, &loc_body.value, Expected::FromAnnotation( name.clone(), arity, AnnotationSource::TypedIfBranch { index: Index::zero_based(index), num_branches: branches.len(), }, tipe.clone(), ), ); branch_cons.push(cond_con); branch_cons.push(then_con); } let else_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, final_else.region, &final_else.value, Expected::FromAnnotation( name, arity, AnnotationSource::TypedIfBranch { index: Index::zero_based(branches.len()), num_branches: branches.len(), }, tipe.clone(), ), ); let ast_con = Eq( Type::Variable(*branch_var), Expected::NoExpectation(tipe), Category::Storage, region, ); branch_cons.push(ast_con); branch_cons.push(else_con); cond_uniq_vars.push(*cond_var); cond_uniq_vars.push(*branch_var); exists(cond_uniq_vars, And(branch_cons)) } _ => { for (index, (loc_cond, loc_body)) in branches.iter().enumerate() { let cond_uniq_var = var_store.fresh(); let expect_bool = Expected::ForReason( Reason::IfCondition, attr_type(Bool::variable(cond_uniq_var), bool_type.clone()), region, ); cond_uniq_vars.push(cond_uniq_var); let cond_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_cond.region, &loc_cond.value, expect_bool, ); let then_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_body.region, &loc_body.value, Expected::ForReason( Reason::IfBranch { index: Index::zero_based(index), total_branches: branches.len(), }, Type::Variable(*branch_var), region, ), ); branch_cons.push(cond_con); branch_cons.push(then_con); } let else_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, final_else.region, &final_else.value, Expected::ForReason( Reason::IfBranch { index: Index::zero_based(branches.len()), total_branches: branches.len(), }, Type::Variable(*branch_var), region, ), ); branch_cons.push(Eq( Type::Variable(*branch_var), expected, Category::If, region, )); branch_cons.push(else_con); cond_uniq_vars.push(*cond_var); cond_uniq_vars.push(*branch_var); exists(cond_uniq_vars, And(branch_cons)) } } } When { cond_var, expr_var, loc_cond, branches, .. } => { let cond_var = *cond_var; let cond_type = Variable(cond_var); let expr_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, region, &loc_cond.value, Expected::NoExpectation(cond_type.clone()), ); let mut constraints = Vec::with_capacity(branches.len() + 1); constraints.push(expr_con); match &expected { Expected::FromAnnotation(name, arity, _, typ) => { constraints.push(Eq( Type::Variable(*expr_var), expected.clone(), Category::When, region, )); for (index, when_branch) in branches.iter().enumerate() { let pattern_region = Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); let branch_con = constrain_when_branch( var_store, var_usage, applied_usage_constraint, env, region, when_branch, PExpected::ForReason( PReason::WhenMatch { index: Index::zero_based(index), }, cond_type.clone(), pattern_region, ), Expected::FromAnnotation( name.clone(), *arity, TypedWhenBranch { index: Index::zero_based(index), }, typ.clone(), ), ); constraints.push( // Each branch's pattern must have the same type // as the condition expression did. branch_con, ); } } _ => { let branch_type = Variable(*expr_var); let mut branch_cons = Vec::with_capacity(branches.len()); for (index, when_branch) in branches.iter().enumerate() { let pattern_region = Region::across_all(when_branch.patterns.iter().map(|v| &v.region)); let branch_con = constrain_when_branch( var_store, var_usage, applied_usage_constraint, env, region, when_branch, PExpected::ForReason( PReason::WhenMatch { index: Index::zero_based(index), }, cond_type.clone(), pattern_region, ), Expected::ForReason( Reason::WhenBranch { index: Index::zero_based(index), }, branch_type.clone(), region, ), ); branch_cons.push(branch_con); } constraints.push(And(vec![ // Each branch's pattern must have the same type // as the condition expression did. And(branch_cons), // The return type of each branch must equal // the return type of the entire case-expression. Eq(branch_type, expected, Category::When, region), ])) } } exists(vec![cond_var, *expr_var], And(constraints)) } Update { record_var, ext_var, symbol, updates, } => { let mut fields: SendMap = SendMap::default(); let mut vars = Vec::with_capacity(updates.len() + 2); let mut cons = Vec::with_capacity(updates.len() + 3); for (field_name, Field { var, loc_expr, .. }) in updates.clone() { let (var, tipe, con) = constrain_field_update( env, var_store, var_usage, applied_usage_constraint, var, region, field_name.clone(), &loc_expr, ); fields.insert(field_name, tipe); vars.push(var); cons.push(con); } let uniq_var = var_store.fresh(); vars.push(uniq_var); let fields_type = attr_type( Bool::variable(uniq_var), Type::Record(fields, Box::new(Type::Variable(*ext_var))), ); let record_type = Type::Variable(*record_var); // NOTE from elm compiler: fields_type is separate so that Error propagates better let fields_con = Eq( record_type.clone(), Expected::NoExpectation(fields_type), Category::Record, region, ); let record_con = Eq(record_type.clone(), expected, Category::Record, region); vars.push(*record_var); vars.push(*ext_var); let con = Lookup( *symbol, Expected::ForReason( Reason::RecordUpdateKeys( *symbol, updates .iter() .map(|(key, field)| (key.clone(), field.region)) .collect(), ), record_type, region, ), region, ); cons.push(con); cons.push(fields_con); cons.push(record_con); exists(vars, And(cons)) } Access { record_var, ext_var, field_var, loc_expr, field, } => { let mut field_types = SendMap::default(); let field_uniq_var = var_store.fresh(); let field_uniq_type = Bool::variable(field_uniq_var); let field_type = attr_type(field_uniq_type, Type::Variable(*field_var)); field_types.insert(field.clone(), field_type.clone()); let record_uniq_var = var_store.fresh(); let record_uniq_type = Bool::container(record_uniq_var, vec![field_uniq_var]); let record_type = attr_type( record_uniq_type, Type::Record(field_types, Box::new(Type::Variable(*ext_var))), ); let category = Category::Access(field.clone()); let record_expected = Expected::NoExpectation(record_type); let record_con = Eq( Type::Variable(*record_var), record_expected.clone(), category.clone(), region, ); let inner_constraint = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_expr.region, &loc_expr.value, record_expected, ); exists( vec![ *record_var, *field_var, *ext_var, field_uniq_var, record_uniq_var, ], And(vec![ Eq(field_type, expected, category, region), inner_constraint, record_con, ]), ) } Accessor { field, record_var, field_var, ext_var, } => { let mut field_types = SendMap::default(); let field_uniq_var = var_store.fresh(); let field_uniq_type = Bool::variable(field_uniq_var); let field_type = attr_type(field_uniq_type, Type::Variable(*field_var)); field_types.insert(field.clone(), field_type.clone()); let record_uniq_var = var_store.fresh(); let record_uniq_type = Bool::container(record_uniq_var, vec![field_uniq_var]); let record_type = attr_type( record_uniq_type, Type::Record(field_types, Box::new(Type::Variable(*ext_var))), ); let category = Category::Accessor(field.clone()); let record_expected = Expected::NoExpectation(record_type.clone()); let record_con = Eq( Type::Variable(*record_var), record_expected, category.clone(), region, ); let fn_uniq_var = var_store.fresh(); let fn_type = attr_type( Bool::variable(fn_uniq_var), Type::Function(vec![record_type], Box::new(field_type)), ); exists( vec![ *record_var, *field_var, *ext_var, fn_uniq_var, field_uniq_var, record_uniq_var, ], And(vec![Eq(fn_type, expected, category, region), record_con]), ) } RuntimeError(_) => True, } } fn constrain_var( var_store: &mut VarStore, applied_usage_constraint: &mut ImSet, symbol_for_lookup: Symbol, usage: Option<&Usage>, region: Region, expected: Expected, ) -> Constraint { use sharing::Mark::*; use sharing::Usage::*; match usage { None | Some(Simple(Shared)) => { // the variable is used/consumed more than once, so it must be Shared let val_var = var_store.fresh(); let uniq_var = var_store.fresh(); let val_type = Variable(val_var); let uniq_type = Bool::variable(uniq_var); let attr_type = attr_type(uniq_type.clone(), val_type); exists( vec![val_var, uniq_var], And(vec![ Lookup(symbol_for_lookup, expected.clone(), region), Eq(attr_type, expected, Category::Uniqueness, region), Eq( Type::Boolean(uniq_type), Expected::NoExpectation(Type::Boolean(Bool::shared())), Category::Uniqueness, region, ), ]), ) } Some(Simple(Unique)) => { // no additional constraints, keep uniqueness unbound Lookup(symbol_for_lookup, expected, region) } Some(Usage::Access(_, _, _)) | Some(Usage::Update(_, _, _)) => { applied_usage_constraint.insert(symbol_for_lookup); let mut variables = Vec::new(); let (record_bool, inner_type) = constrain_by_usage(&usage.expect("wut"), var_store, &mut variables); let record_type = attr_type(record_bool, inner_type); // NOTE breaking the expectation up like this REALLY matters! let new_expected = Expected::NoExpectation(record_type.clone()); exists( variables, And(vec![ Lookup(symbol_for_lookup, new_expected, region), Eq(record_type, expected, Category::Uniqueness, region), ]), ) } Some(other) => panic!("some other rc value: {:?}", other), } } fn constrain_by_usage( usage: &Usage, var_store: &mut VarStore, introduced: &mut Vec, ) -> (Bool, Type) { use Usage::*; match usage { Simple(Mark::Shared) => { let var = var_store.fresh(); introduced.push(var); (Bool::Shared, Type::Variable(var)) } Simple(Mark::Seen) | Simple(Mark::Unique) => { let var = var_store.fresh(); let uvar = var_store.fresh(); introduced.push(var); introduced.push(uvar); (Bool::container(uvar, vec![]), Type::Variable(var)) } Usage::Access(Container::Record, mark, fields) => { let (record_bool, ext_type) = constrain_by_usage(&Simple(*mark), var_store, introduced); constrain_by_usage_record(fields, record_bool, ext_type, introduced, var_store) } Usage::Update(Container::Record, _, fields) => { let record_uvar = var_store.fresh(); introduced.push(record_uvar); let record_bool = Bool::variable(record_uvar); let ext_var = var_store.fresh(); let ext_type = Type::Variable(ext_var); introduced.push(ext_var); constrain_by_usage_record(fields, record_bool, ext_type, introduced, var_store) } Usage::Access(Container::List, mark, fields) => { let (list_bool, _ext_type) = constrain_by_usage(&Simple(*mark), var_store, introduced); let field_usage = fields .get(&sharing::LIST_ELEM.into()) .expect("no LIST_ELEM key"); let (elem_bool, elem_type) = constrain_by_usage(field_usage, var_store, introduced); match list_bool { Bool::Shared => ( Bool::Shared, Type::Apply(Symbol::LIST_LIST, vec![attr_type(Bool::Shared, elem_type)]), ), Bool::Container(list_uvar, list_mvars) => { debug_assert!(list_mvars.is_empty()); match elem_bool { Bool::Shared => ( Bool::variable(list_uvar), Type::Apply( Symbol::LIST_LIST, vec![attr_type(Bool::Shared, elem_type)], ), ), Bool::Container(cvar, mvars) => { debug_assert!(mvars.is_empty()); ( Bool::container(list_uvar, vec![cvar]), Type::Apply( Symbol::LIST_LIST, vec![attr_type(Bool::container(cvar, mvars), elem_type)], ), ) } } } } } Usage::Update(Container::List, _, fields) => { let list_uvar = var_store.fresh(); introduced.push(list_uvar); let 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_record( fields: &FieldAccess, record_bool: Bool, ext_type: Type, introduced: &mut Vec, var_store: &mut VarStore, ) -> (Bool, Type) { let mut field_types = SendMap::default(); match record_bool { _ if fields.is_empty() => (record_bool, Type::Record(field_types, Box::new(ext_type))), Bool::Shared => { for (lowercase, nested_usage) in fields.clone().into_iter() { let (_, nested_type) = constrain_by_usage(&nested_usage, var_store, introduced); let field_type = attr_type(Bool::Shared, nested_type); field_types.insert(lowercase.clone(), field_type); } ( Bool::Shared, Type::Record( field_types, // TODO can we avoid doing Box::new on every single one of these? // For example, could we have a single lazy_static global Box they // could all share? Box::new(ext_type), ), ) } Bool::Container(record_uniq_var, mvars) => { debug_assert!(mvars.is_empty()); let mut uniq_vars = Vec::with_capacity(fields.len()); for (lowercase, nested_usage) in fields.clone().into_iter() { let (nested_bool, nested_type) = constrain_by_usage(&nested_usage, var_store, introduced); let field_type = match nested_bool { Bool::Container(uvar, atoms) => { for atom in &atoms { uniq_vars.push(*atom); } uniq_vars.push(uvar); attr_type(Bool::Container(uvar, atoms), nested_type) } Bool::Shared => attr_type(Bool::Shared, nested_type), }; field_types.insert(lowercase.clone(), field_type); } ( Bool::container(record_uniq_var, uniq_vars), Type::Record( field_types, // TODO can we avoid doing Box::new on every single one of these? // For example, could we have a single lazy_static global Box they // could all share? Box::new(ext_type), ), ) } } } // TODO trim down these arguments #[allow(clippy::too_many_arguments)] // NOTE enabling the inline pragma can blow the stack in debug mode // #[inline(always)] fn constrain_when_branch( var_store: &mut VarStore, var_usage: &VarUsage, applied_usage_constraint: &mut ImSet, env: &Env, region: Region, when_branch: &WhenBranch, pattern_expected: PExpected, expr_expected: Expected, ) -> Constraint { let ret_constraint = constrain_expr( env, var_store, var_usage, applied_usage_constraint, region, &when_branch.value.value, expr_expected, ); let mut state = PatternState { headers: SendMap::default(), vars: Vec::with_capacity(1), constraints: Vec::with_capacity(1), }; for loc_pattern in &when_branch.patterns { // mutates the state, so return value is not used constrain_pattern( var_store, &mut state, &loc_pattern, pattern_expected.clone(), ); } if let Some(loc_guard) = &when_branch.guard { let guard_uniq_var = var_store.fresh(); let bool_type = attr_type( Bool::variable(guard_uniq_var), Type::Variable(Variable::BOOL), ); let guard_constraint = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_guard.region, &loc_guard.value, Expected::ForReason(Reason::WhenGuard, bool_type, loc_guard.region), ); Constraint::Let(Box::new(LetConstraint { rigid_vars: Vec::new(), flex_vars: state.vars, def_types: state.headers, def_aliases: SendMap::default(), defs_constraint: Constraint::And(state.constraints), ret_constraint: Constraint::Let(Box::new(LetConstraint { rigid_vars: Vec::new(), flex_vars: vec![guard_uniq_var], def_types: SendMap::default(), def_aliases: SendMap::default(), defs_constraint: guard_constraint, ret_constraint, })), })) } else { Constraint::Let(Box::new(LetConstraint { rigid_vars: Vec::new(), flex_vars: state.vars, def_types: state.headers, def_aliases: SendMap::default(), defs_constraint: Constraint::And(state.constraints), ret_constraint, })) } } fn constrain_def_pattern( var_store: &mut VarStore, loc_pattern: &Located, expr_type: Type, ) -> PatternState { // Exclude the current ident from shadowable_idents; you can't shadow yourself! // (However, still include it in scope, because you *can* recursively refer to yourself.) let pattern_expected = PExpected::NoExpectation(expr_type); let mut state = PatternState { headers: SendMap::default(), vars: Vec::with_capacity(1), constraints: Vec::with_capacity(1), }; constrain_pattern(var_store, &mut state, loc_pattern, pattern_expected); state } /// Turn e.g. `Int` into `Attr.Attr * Int` fn annotation_to_attr_type( var_store: &mut VarStore, ann: &Type, rigids: &mut ImSet, change_var_kind: bool, ) -> (Vec, Type) { use roc_types::types::Type::*; match ann { Variable(var) => { if change_var_kind { let uvar = var_store.fresh(); rigids.insert(uvar); ( vec![], attr_type(Bool::variable(uvar), Type::Variable(*var)), ) } else { (vec![], Type::Variable(*var)) } } Boolean(_) | Erroneous(_) => (vec![], ann.clone()), EmptyRec | EmptyTagUnion => { let uniq_var = var_store.fresh(); ( vec![uniq_var], attr_type(Bool::variable(uniq_var), ann.clone()), ) } Function(arguments, result) => { let uniq_var = var_store.fresh(); let (mut arg_vars, args_lifted) = annotation_to_attr_type_many(var_store, arguments, rigids, change_var_kind); let (result_vars, result_lifted) = annotation_to_attr_type(var_store, result, rigids, change_var_kind); arg_vars.extend(result_vars); arg_vars.push(uniq_var); ( arg_vars, attr_type( Bool::variable(uniq_var), Type::Function(args_lifted, Box::new(result_lifted)), ), ) } Apply(Symbol::ATTR_ATTR, args) => { let uniq_type = args[0].clone(); // A rigid behind an attr has already been lifted, don't do it again! let (result_vars, result_lifted) = match args[1] { Type::Variable(_) => match uniq_type { Type::Boolean(Bool::Container(urigid, _)) => (vec![urigid], args[1].clone()), _ => (vec![], args[1].clone()), }, _ => annotation_to_attr_type(var_store, &args[1], rigids, change_var_kind), }; let result = Apply(Symbol::ATTR_ATTR, vec![uniq_type, result_lifted]); (result_vars, result) } Apply(symbol, args) => { let uniq_var = var_store.fresh(); let (mut arg_vars, args_lifted) = annotation_to_attr_type_many(var_store, args, rigids, change_var_kind); let result = attr_type(Bool::variable(uniq_var), Type::Apply(*symbol, args_lifted)); arg_vars.push(uniq_var); (arg_vars, result) } Record(fields, ext_type) => { let uniq_var = var_store.fresh(); let mut vars = Vec::with_capacity(fields.len()); let mut lifted_fields = SendMap::default(); for (label, tipe) in fields.clone() { let (new_vars, lifted_field) = annotation_to_attr_type(var_store, &tipe, rigids, change_var_kind); vars.extend(new_vars); lifted_fields.insert(label, lifted_field); } vars.push(uniq_var); ( vars, attr_type( Bool::variable(uniq_var), Type::Record(lifted_fields, ext_type.clone()), ), ) } TagUnion(tags, ext_type) => { let uniq_var = var_store.fresh(); let mut vars = Vec::with_capacity(tags.len()); let mut lifted_tags = Vec::with_capacity(tags.len()); for (tag, fields) in tags { let (new_vars, lifted_fields) = annotation_to_attr_type_many(var_store, fields, rigids, change_var_kind); vars.extend(new_vars); lifted_tags.push((tag.clone(), lifted_fields)); } vars.push(uniq_var); ( vars, attr_type( Bool::variable(uniq_var), Type::TagUnion(lifted_tags, ext_type.clone()), ), ) } RecursiveTagUnion(rec_var, tags, ext_type) => { // In the case of // // [ Cons a (List a), Nil ] as List a // // We need to lift it to // // Attr u ([ Cons a (Attr u (List a)), Nil ] as List a) // // So the `u` of the whole recursive tag union is the same as the one used in the recursion let uniq_var = var_store.fresh(); let mut vars = Vec::with_capacity(tags.len()); let mut lifted_tags = Vec::with_capacity(tags.len()); let mut substitutions = ImMap::default(); substitutions.insert( *rec_var, attr_type(Bool::variable(uniq_var), Type::Variable(*rec_var)), ); for (tag, fields) in tags { let (new_vars, mut lifted_fields) = annotation_to_attr_type_many(var_store, fields, rigids, change_var_kind); vars.extend(new_vars); for f in lifted_fields.iter_mut() { f.substitute(&substitutions); } lifted_tags.push((tag.clone(), lifted_fields)); } vars.push(uniq_var); let result = attr_type( Bool::variable(uniq_var), Type::RecursiveTagUnion(*rec_var, lifted_tags, ext_type.clone()), ); (vars, result) } Alias(symbol, fields, actual) => { let (mut actual_vars, lifted_actual) = annotation_to_attr_type(var_store, actual, rigids, change_var_kind); if let Type::Apply(attr_symbol, args) = lifted_actual { debug_assert!(attr_symbol == Symbol::ATTR_ATTR); let uniq_type = args[0].clone(); let actual_type = args[1].clone(); let mut new_fields = Vec::with_capacity(fields.len()); for (name, tipe) in fields { let (lifted_vars, lifted) = annotation_to_attr_type(var_store, tipe, rigids, change_var_kind); actual_vars.extend(lifted_vars); new_fields.push((name.clone(), lifted)); } let alias = Type::Alias(*symbol, new_fields, Box::new(actual_type)); ( actual_vars, crate::builtins::builtin_type(Symbol::ATTR_ATTR, vec![uniq_type, alias]), ) } else { panic!("lifted type is not Attr") } } } } fn annotation_to_attr_type_many( var_store: &mut VarStore, anns: &[Type], rigids: &mut ImSet, change_var_kind: bool, ) -> (Vec, Vec) { anns.iter() .fold((Vec::new(), Vec::new()), |(mut vars, mut types), value| { let (new_vars, tipe) = annotation_to_attr_type(var_store, value, rigids, change_var_kind); vars.extend(new_vars); types.push(tipe); (vars, types) }) } fn aliases_to_attr_type(var_store: &mut VarStore, aliases: &mut SendMap) { for alias in aliases.iter_mut() { // ensure // // Identity a : [ Identity a ] // // does not turn into // // Identity a : [ Identity (Attr u a) ] // // That would give a double attr wrapper on the type arguments. // The `change_var_kind` flag set to false ensures type variables remain of kind * let (_, new) = annotation_to_attr_type(var_store, &alias.typ, &mut ImSet::default(), false); // remove the outer Attr, because when this occurs in a signature it'll already be wrapped in one match new { Type::Apply(Symbol::ATTR_ATTR, args) => { alias.typ = args[1].clone(); if let Type::Boolean(b) = args[0].clone() { alias.uniqueness = Some(b); } } _ => unreachable!("`annotation_to_attr_type` always gives back an Attr"), } // Check that if the alias is a recursive tag union, all structures containing the // recursion variable get the same uniqueness as the recursion variable (and thus as the // recursive tag union itself) if let Some(b) = &alias.uniqueness { fix_mutual_recursive_alias(&mut alias.typ, b); } } } fn constrain_def( env: &Env, var_store: &mut VarStore, var_usage: &VarUsage, applied_usage_constraint: &mut ImSet, def: &Def, body_con: Constraint, ) -> Constraint { let expr_var = def.expr_var; let expr_type = Type::Variable(expr_var); let mut pattern_state = constrain_def_pattern(var_store, &def.loc_pattern, expr_type.clone()); pattern_state.vars.push(expr_var); let mut def_aliases = SendMap::default(); let mut new_rigids = Vec::new(); let expr_con = match &def.annotation { Some(annotation) => { def_aliases = annotation.aliases.clone(); let arity = annotation.signature.arity(); let mut ftv = env.rigids.clone(); let signature = instantiate_rigids( var_store, &annotation.signature, &annotation.introduced_variables, &mut new_rigids, &mut ftv, &def.loc_pattern, &mut pattern_state.headers, ); let annotation_expected = Expected::FromAnnotation( def.loc_pattern.clone(), arity, AnnotationSource::TypedBody { region: annotation.region, }, signature, ); pattern_state.constraints.push(Eq( expr_type, annotation_expected.clone(), Category::Storage, Region::zero(), )); constrain_expr( &Env { rigids: ftv, home: env.home, }, var_store, var_usage, applied_usage_constraint, def.loc_expr.region, &def.loc_expr.value, annotation_expected, ) } None => constrain_expr( env, var_store, var_usage, applied_usage_constraint, def.loc_expr.region, &def.loc_expr.value, Expected::NoExpectation(expr_type), ), }; // Lift aliases to Attr types aliases_to_attr_type(var_store, &mut def_aliases); Let(Box::new(LetConstraint { rigid_vars: new_rigids, flex_vars: pattern_state.vars, def_types: pattern_state.headers, def_aliases, defs_constraint: Let(Box::new(LetConstraint { rigid_vars: Vec::new(), // always empty flex_vars: Vec::new(), // empty, because our functions have no arguments def_types: SendMap::default(), // empty, because our functions have no arguments! def_aliases: SendMap::default(), defs_constraint: And(pattern_state.constraints), ret_constraint: expr_con, })), ret_constraint: body_con, })) } fn instantiate_rigids( var_store: &mut VarStore, annotation: &Type, introduced_vars: &IntroducedVariables, new_rigids: &mut Vec, ftv: &mut ImMap, loc_pattern: &Located, headers: &mut SendMap>, ) -> Type { let unlifed_annotation = annotation.clone(); let mut annotation = annotation.clone(); let mut rigid_substitution: ImMap = ImMap::default(); for (name, var) in introduced_vars.var_by_name.iter() { if let Some((existing_rigid, existing_uvar)) = ftv.get(&name) { rigid_substitution.insert( *var, attr_type( Bool::variable(*existing_uvar), Type::Variable(*existing_rigid), ), ); } else { // possible use this rigid in nested def's let uvar = var_store.fresh(); ftv.insert(name.clone(), (*var, uvar)); new_rigids.push(*var); } } // Instantiate rigid variables if !rigid_substitution.is_empty() { annotation.substitute(&rigid_substitution); } let mut new_uniqueness_rigids = ImSet::default(); let (mut uniq_vars, annotation) = annotation_to_attr_type(var_store, &annotation, &mut new_uniqueness_rigids, true); if let Pattern::Identifier(symbol) = loc_pattern.value { headers.insert(symbol, Located::at(loc_pattern.region, annotation.clone())); } else if let Some(new_headers) = crate::pattern::headers_from_annotation( &loc_pattern.value, &Located::at(loc_pattern.region, unlifed_annotation), ) { for (k, v) in new_headers { let (new_uniq_vars, attr_annotation) = annotation_to_attr_type(var_store, &v.value, &mut new_uniqueness_rigids, true); uniq_vars.extend(new_uniq_vars); headers.insert(k, Located::at(loc_pattern.region, attr_annotation)); } } new_rigids.extend(uniq_vars); new_rigids.extend(introduced_vars.wildcards.iter().cloned()); new_rigids.extend(new_uniqueness_rigids); annotation } fn constrain_recursive_defs( env: &Env, var_store: &mut VarStore, var_usage: &VarUsage, applied_usage_constraint: &mut ImSet, defs: &[Def], body_con: Constraint, ) -> Constraint { rec_defs_help( env, var_store, var_usage, applied_usage_constraint, defs, body_con, Info::with_capacity(defs.len()), Info::with_capacity(defs.len()), ) } #[allow(clippy::too_many_arguments)] pub fn rec_defs_help( env: &Env, var_store: &mut VarStore, var_usage: &VarUsage, applied_usage_constraint: &mut ImSet, defs: &[Def], body_con: Constraint, mut rigid_info: Info, mut flex_info: Info, ) -> Constraint { let mut def_aliases = SendMap::default(); for def in defs { let expr_var = def.expr_var; let expr_type = Type::Variable(expr_var); let pattern_expected = PExpected::NoExpectation(expr_type.clone()); let mut pattern_state = PatternState { headers: SendMap::default(), vars: flex_info.vars.clone(), constraints: Vec::with_capacity(1), }; pattern_state.vars.push(expr_var); constrain_pattern( var_store, &mut pattern_state, &def.loc_pattern, pattern_expected, ); // TODO see where aliases should go let mut new_rigids = Vec::new(); match &def.annotation { None => { let expr_con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, def.loc_expr.region, &def.loc_expr.value, Expected::NoExpectation(expr_type), ); // TODO investigate if this let can be safely removed let def_con = Let(Box::new(LetConstraint { rigid_vars: Vec::new(), flex_vars: Vec::new(), // empty because Roc function defs have no args def_types: SendMap::default(), // empty because Roc function defs have no args def_aliases: SendMap::default(), defs_constraint: True, // I think this is correct, once again because there are no args ret_constraint: expr_con, })); flex_info.vars = pattern_state.vars; flex_info.constraints.push(def_con); flex_info.def_types.extend(pattern_state.headers); } Some(annotation) => { for (symbol, alias) in annotation.aliases.clone() { def_aliases.insert(symbol, alias); } let arity = annotation.signature.arity(); let mut ftv = env.rigids.clone(); let signature = instantiate_rigids( var_store, &annotation.signature, &annotation.introduced_variables, &mut new_rigids, &mut ftv, &def.loc_pattern, &mut pattern_state.headers, ); let annotation_expected = Expected::FromAnnotation( def.loc_pattern.clone(), arity, AnnotationSource::TypedBody { region: annotation.region, }, signature.clone(), ); let expr_con = constrain_expr( &Env { rigids: ftv, home: env.home, }, var_store, var_usage, applied_usage_constraint, def.loc_expr.region, &def.loc_expr.value, Expected::NoExpectation(expr_type.clone()), ); // ensure expected type unifies with annotated type rigid_info.constraints.push(Eq( expr_type, annotation_expected.clone(), Category::Storage, def.loc_expr.region, )); // TODO investigate if this let can be safely removed let def_con = Let(Box::new(LetConstraint { rigid_vars: Vec::new(), flex_vars: Vec::new(), // empty because Roc function defs have no args def_types: SendMap::default(), // empty because Roc function defs have no args def_aliases: SendMap::default(), defs_constraint: True, // I think this is correct, once again because there are no args ret_constraint: expr_con, })); rigid_info.vars.extend(&new_rigids); // because of how in Roc headers point to variables, we must include the pattern var here rigid_info.vars.extend(pattern_state.vars); rigid_info.constraints.push(Let(Box::new(LetConstraint { rigid_vars: new_rigids, flex_vars: Vec::new(), // no flex vars introduced def_types: SendMap::default(), // no headers introduced (at this level) def_aliases: SendMap::default(), defs_constraint: def_con, ret_constraint: True, }))); rigid_info.def_types.extend(pattern_state.headers); } } } // list aliases to Attr types aliases_to_attr_type(var_store, &mut def_aliases); Let(Box::new(LetConstraint { rigid_vars: rigid_info.vars, flex_vars: Vec::new(), def_types: rigid_info.def_types, def_aliases, defs_constraint: True, ret_constraint: Let(Box::new(LetConstraint { rigid_vars: Vec::new(), flex_vars: flex_info.vars, def_types: flex_info.def_types.clone(), def_aliases: SendMap::default(), defs_constraint: Let(Box::new(LetConstraint { rigid_vars: Vec::new(), flex_vars: Vec::new(), def_types: flex_info.def_types, def_aliases: SendMap::default(), defs_constraint: True, ret_constraint: And(flex_info.constraints), })), ret_constraint: And(vec![And(rigid_info.constraints), body_con]), })), })) } #[allow(clippy::too_many_arguments)] #[inline(always)] fn constrain_field_update( env: &Env, var_store: &mut VarStore, var_usage: &VarUsage, applied_usage_constraint: &mut ImSet, var: Variable, region: Region, field: Lowercase, loc_expr: &Located, ) -> (Variable, Type, Constraint) { let field_type = Type::Variable(var); let reason = Reason::RecordUpdateValue(field); let expected = Expected::ForReason(reason, field_type.clone(), region); let con = constrain_expr( env, var_store, var_usage, applied_usage_constraint, loc_expr.region, &loc_expr.value, expected, ); (var, field_type, con) } /// Fix uniqueness attributes on mutually recursive type aliases. /// Given aliases /// /// > ListA a b : [ Cons a (ListB b a), Nil ] /// > ListB a b : [ Cons a (ListA b a), Nil ] /// /// We get the lifted alias: /// /// > `Test.ListB`: Alias { /// > ..., /// > uniqueness: Some( /// > Container( /// > 118, /// > {}, /// > ), /// > ), /// > typ: [ Global('Cons') <9> (`#Attr.Attr` Container(119, {}) Alias `Test.ListA` <10> <9>[ but actually [ Global('Cons') <10> (`#Attr.Attr` Container(118, {}) <13>), Global('Nil') ] ]), Global('Nil') ] as <13>, /// > }, /// /// Note that the alias will get uniqueness variable <118>, but the contained `ListA` gets variable /// <119>. But, 119 is contained in 118, and 118 in 119, so we need <119> >= <118> >= <119> >= <118> ... /// That can only be true if they are the same. Type inference will not find that, so we must do it /// ourselves in user-defined aliases. fn fix_mutual_recursive_alias(typ: &mut Type, attribute: &Bool) { use Type::*; if let RecursiveTagUnion(rec, tags, _ext) = typ { for (_, args) in tags { for mut arg in args { fix_mutual_recursive_alias_help(*rec, &Type::Boolean(attribute.clone()), &mut arg); } } } } fn fix_mutual_recursive_alias_help(rec_var: Variable, attribute: &Type, into_type: &mut Type) { if into_type.contains_variable(rec_var) { if let Type::Apply(Symbol::ATTR_ATTR, args) = into_type { std::mem::replace(&mut args[0], attribute.clone()); fix_mutual_recursive_alias_help_help(rec_var, attribute, &mut args[1]); } } } #[inline(always)] fn fix_mutual_recursive_alias_help_help(rec_var: Variable, attribute: &Type, into_type: &mut Type) { use Type::*; match into_type { Function(args, ret) => { fix_mutual_recursive_alias_help(rec_var, attribute, ret); args.iter_mut() .for_each(|arg| fix_mutual_recursive_alias_help(rec_var, attribute, arg)); } RecursiveTagUnion(_, tags, ext) | TagUnion(tags, ext) => { fix_mutual_recursive_alias_help(rec_var, attribute, ext); for (_tag, args) in tags.iter_mut() { for arg in args.iter_mut() { fix_mutual_recursive_alias_help(rec_var, attribute, arg); } } } Record(fields, ext) => { fix_mutual_recursive_alias_help(rec_var, attribute, ext); fields .iter_mut() .for_each(|arg| fix_mutual_recursive_alias_help(rec_var, attribute, arg)); } Alias(_, _, actual_type) => { // call help_help, because actual_type is not wrapped in ATTR fix_mutual_recursive_alias_help_help(rec_var, attribute, actual_type); } Apply(_, args) => { args.iter_mut() .for_each(|arg| fix_mutual_recursive_alias_help(rec_var, attribute, arg)); } EmptyRec | EmptyTagUnion | Erroneous(_) | Variable(_) | Boolean(_) => {} } }