diff --git a/src/can/module.rs b/src/can/module.rs index 1afcce7fb0..2b4a2fa9d0 100644 --- a/src/can/module.rs +++ b/src/can/module.rs @@ -2,6 +2,7 @@ use crate::can::def::{canonicalize_defs, sort_can_defs, Declaration}; use crate::can::env::Env; use crate::can::expr::Output; use crate::can::ident::Ident; +use crate::can::ident::Lowercase; use crate::can::operator::desugar_def; use crate::can::pattern::PatternType; use crate::can::problem::RuntimeError; @@ -17,6 +18,7 @@ use bumpalo::Bump; #[derive(Debug)] pub struct ModuleOutput { pub aliases: MutMap, + pub rigid_variables: MutMap, pub declarations: Vec, pub exposed_imports: MutMap, pub lookups: Vec<(Symbol, Variable, Region)>, @@ -61,6 +63,7 @@ pub fn canonicalize_module_defs<'a>( let mut env = Env::new(home, dep_idents, module_ids, exposed_ident_ids); let mut lookups = Vec::with_capacity(num_deps); + let mut rigid_variables = MutMap::default(); // Exposed values are treated like defs that appear before any others, e.g. // @@ -109,6 +112,10 @@ pub fn canonicalize_module_defs<'a>( PatternType::TopLevelDef, ); + for (var, lowercase) in output.rigids.clone() { + rigid_variables.insert(var, lowercase); + } + let mut references = MutSet::default(); // Gather up all the symbols that were referenced across all the defs. @@ -190,6 +197,7 @@ pub fn canonicalize_module_defs<'a>( Ok(ModuleOutput { aliases, + rigid_variables, declarations, references, exposed_imports: can_exposed_imports, diff --git a/src/constrain/module.rs b/src/constrain/module.rs index 529b18233e..b04c618491 100644 --- a/src/constrain/module.rs +++ b/src/constrain/module.rs @@ -1,5 +1,6 @@ use crate::can::def::Declaration; -use crate::collections::SendMap; +use crate::can::ident::Lowercase; +use crate::collections::{ImMap, MutMap, SendMap}; use crate::constrain::expr::constrain_decls; use crate::module::symbol::{ModuleId, Symbol}; use crate::region::{Located, Region}; @@ -25,22 +26,33 @@ pub struct Import<'a> { pub fn constrain_imported_values( imports: Vec>, + aliases: MutMap, mut body_con: Constraint, var_store: &VarStore, ) -> Constraint { - dbg!(&imports); // TODO try out combining all the def_types and free_vars, so that we // don't need to make this big linked list of nested constraints. // Theoretically that should be equivalent to doing it this way! for import in imports { - body_con = - constrain_imported_value(import.loc_symbol, import.solved_type, body_con, var_store); + body_con = constrain_imported_value( + import.loc_symbol, + import.solved_type, + body_con, + &aliases, + var_store, + ); } body_con } -fn to_type(solved_type: &SolvedType, free_vars: &mut Vec, var_store: &VarStore) -> Type { +#[derive(Debug, Clone, Default)] +pub struct FreeVars { + rigid_vars: ImMap, + flex_vars: Vec, +} + +fn to_type(solved_type: &SolvedType, free_vars: &mut FreeVars, var_store: &VarStore) -> Type { use crate::solve::SolvedType::*; match solved_type { @@ -64,16 +76,19 @@ fn to_type(solved_type: &SolvedType, free_vars: &mut Vec, var_store: & Type::Apply(*symbol, new_args) } - Rigid(symbol) => { - // TODO: I guess the design here is that we need to record - // rigids after all, and incorporate them into - // the final constraint? Just to make sure the types - // actually line up? Should take a look at how - // this is done in the Defs annotations. - panic!("TODO to_type for a rigid: {:?}", symbol); + Rigid(lowercase) => { + if let Some(var) = free_vars.rigid_vars.get(&lowercase) { + Type::Variable(*var) + } else { + let var = var_store.fresh(); + free_vars.rigid_vars.insert(lowercase.clone(), var); + Type::Variable(var) + } } Wildcard => { - panic!("TODO flex variable for wildcard"); + let var = var_store.fresh(); + free_vars.flex_vars.push(var); + Type::Variable(var) } Record { fields, ext } => { let mut new_fields = SendMap::default(); @@ -127,41 +142,20 @@ fn constrain_imported_value( loc_symbol: Located, solved_type: &SolvedType, body_con: Constraint, + aliases: &MutMap, var_store: &VarStore, ) -> Constraint { use Constraint::*; - let mut free_vars = Vec::new(); + let mut free_vars = FreeVars::default(); // an imported symbol can be both an alias and a value match solved_type { - SolvedType::Alias(symbol, args, solved_actual) if symbol == &loc_symbol.value => { - let mut def_aliases = SendMap::default(); - - let actual = to_type(solved_actual, &mut free_vars, var_store); - - // pub vars: Vec>, - let vars = Vec::new(); - - let alias = Alias { - region: loc_symbol.region, - vars, - typ: actual, - }; - - def_aliases.insert(*symbol, alias); - - Let(Box::new(LetConstraint { - // rigids from other modules should not be treated as rigid - // within this module; rather, they should be treated as flex - rigid_vars: Vec::new(), - flex_vars: Vec::new(), - // Importing a value doesn't constrain this module at all. - // All it does is introduce variables and provide def_types for lookups - def_types: SendMap::default(), - def_aliases, - defs_constraint: True, - ret_constraint: body_con, - })) + SolvedType::Alias(symbol, _, _) if symbol == &loc_symbol.value => { + if let Some(alias) = aliases.get(symbol) { + constrain_imported_alias(loc_symbol, alias, body_con, var_store) + } else { + panic!("Alias {:?} is not available", symbol) + } } _ => { let mut def_types = SendMap::default(); @@ -175,11 +169,17 @@ fn constrain_imported_value( }, ); + let mut rigid_vars = Vec::new(); + + for (_, var) in free_vars.rigid_vars { + rigid_vars.push(var); + } + Let(Box::new(LetConstraint { // rigids from other modules should not be treated as rigid // within this module; rather, they should be treated as flex - rigid_vars: Vec::new(), - flex_vars: free_vars, + rigid_vars, + flex_vars: Vec::new(), // Importing a value doesn't constrain this module at all. // All it does is introduce variables and provide def_types for lookups def_types, @@ -190,3 +190,49 @@ fn constrain_imported_value( } } } + +fn constrain_imported_alias( + loc_symbol: Located, + imported_alias: &Alias, + body_con: Constraint, + var_store: &VarStore, +) -> Constraint { + use Constraint::*; + let mut def_aliases = SendMap::default(); + + let mut vars = Vec::with_capacity(imported_alias.vars.len()); + let mut substitution = ImMap::default(); + for Located { + region, + value: (lowercase, old_var), + } in &imported_alias.vars + { + let new_var = var_store.fresh(); + vars.push(Located::at(*region, (lowercase.clone(), new_var))); + substitution.insert(*old_var, Type::Variable(new_var)); + } + + let mut actual = imported_alias.typ.clone(); + actual.substitute(&substitution); + + let alias = Alias { + vars, + region: loc_symbol.region, + typ: actual, + }; + + def_aliases.insert(loc_symbol.value, alias); + + Let(Box::new(LetConstraint { + // rigids from other modules should not be treated as rigid + // within this module; rather, they should be treated as flex + rigid_vars: Vec::new(), + flex_vars: Vec::new(), + // Importing a value doesn't constrain this module at all. + // All it does is introduce variables and provide def_types for lookups + def_types: SendMap::default(), + def_aliases, + defs_constraint: True, + ret_constraint: body_con, + })) +} diff --git a/src/ena/unify/mod.rs b/src/ena/unify/mod.rs index d1bd3aed91..de12c36a00 100644 --- a/src/ena/unify/mod.rs +++ b/src/ena/unify/mod.rs @@ -311,7 +311,7 @@ impl UnificationTable { } } - pub fn is_redirect(&mut self, vid: S::Key) -> bool { + pub fn is_redirect(&self, vid: S::Key) -> bool { self.value(vid).raw_parent() != vid } diff --git a/src/load/mod.rs b/src/load/mod.rs index 25d3fda226..a02d41bc9a 100644 --- a/src/load/mod.rs +++ b/src/load/mod.rs @@ -1,5 +1,5 @@ use crate::can::def::Declaration; -use crate::can::ident::{Ident, ModuleName}; +use crate::can::ident::{Ident, Lowercase, ModuleName}; use crate::can::module::{canonicalize_module_defs, ModuleOutput}; use crate::collections::{default_hasher, insert_all, MutMap, MutSet, SendMap}; use crate::constrain::module::{constrain_imported_values, constrain_module, Import}; @@ -34,6 +34,7 @@ pub struct Module { pub exposed_vars_by_symbol: Vec<(Symbol, Variable)>, pub references: MutSet, pub aliases: MutMap, + pub rigid_variables: MutMap, } pub struct LoadedModule { @@ -77,6 +78,7 @@ enum Msg { Solved { module_id: ModuleId, solved_types: MutMap, + aliases: MutMap, subs: Arc>, problems: Vec, }, @@ -372,6 +374,7 @@ pub async fn load<'a>( solved_types, subs, problems, + aliases, .. } => { all_problems.extend(problems); @@ -410,7 +413,8 @@ pub async fn load<'a>( } else { // This was a dependency. Write it down and keep processing messages. debug_assert!(!exposed_types.contains_key(&module_id)); - exposed_types.insert(module_id, ExposedModuleTypes::Valid(solved_types)); + exposed_types + .insert(module_id, ExposedModuleTypes::Valid(solved_types, aliases)); // Notify all the listeners that this solved. if let Some(listeners) = solve_listeners.remove(&module_id) { @@ -707,6 +711,7 @@ fn solve_module( ) { let home = module.module_id; let mut imports = Vec::with_capacity(module.references.len()); + let mut aliases = MutMap::default(); // Translate referenced symbols into constraints for &symbol in module.references.iter() { @@ -721,7 +726,7 @@ fn solve_module( }; match exposed_types.get(&module_id) { - Some(ExposedModuleTypes::Valid(solved_types)) => { + Some(ExposedModuleTypes::Valid(solved_types, new_aliases)) => { let solved_type = solved_types.get(&loc_symbol.value).unwrap_or_else(|| { panic!( "Could not find {:?} in solved_types {:?}", @@ -729,6 +734,11 @@ fn solve_module( ) }); + // TODO should this be a union? + for (k, v) in new_aliases.clone() { + aliases.insert(k, v); + } + imports.push(Import { loc_symbol, solved_type, @@ -753,7 +763,7 @@ fn solve_module( } // Wrap the existing module constraint in these imported constraints. - let constraint = constrain_imported_values(imports, constraint, &var_store); + let constraint = constrain_imported_values(imports, aliases, constraint, &var_store); // All the exposed imports should be available in the solver's vars_by_symbol for (symbol, expr_var) in module.exposed_imports.iter() { @@ -787,18 +797,22 @@ fn solve_module( aliases.insert(symbol, alias); } + let env = solve::Env { + vars_by_symbol, + aliases: aliases.clone(), + }; + + let mut subs = Subs::new(var_store.into()); + for (var, name) in module.rigid_variables { + subs.rigid_var(var, name); + } + // Start solving this module in the background. spawn_blocking(move || { // Now that the module is parsed, canonicalized, and constrained, // we need to type check it. - let subs = Subs::new(var_store.into()); let mut problems = Vec::new(); - let env = solve::Env { - vars_by_symbol, - aliases, - }; - // Run the solver to populate Subs. let (solved_subs, solved_env) = solve::run(&env, &mut problems, subs, &constraint); @@ -842,6 +856,7 @@ fn solve_module( subs: Arc::new(solved_subs), solved_types, problems, + aliases: aliases.clone().into_iter().collect(), }) .await .unwrap_or_else(|_| panic!("Failed to send Solved message")); @@ -944,6 +959,7 @@ fn parse_and_constrain( exposed_vars_by_symbol, references, aliases, + rigid_variables, .. }) => { let constraint = constrain_module(module_id, &declarations, lookups); @@ -955,6 +971,7 @@ fn parse_and_constrain( exposed_vars_by_symbol, references, aliases, + rigid_variables, }; (module, ident_ids, constraint) diff --git a/src/solve.rs b/src/solve.rs index cae3473948..b56743cda9 100644 --- a/src/solve.rs +++ b/src/solve.rs @@ -19,7 +19,7 @@ pub type SubsByModule = MutMap; #[derive(Clone, Debug)] pub enum ExposedModuleTypes { Invalid, - Valid(MutMap), + Valid(MutMap, MutMap), } /// This is a fully solved type, with no Variables remaining in it. @@ -139,8 +139,9 @@ impl SolvedType { SolvedType::Alias(symbol, solved_args, Box::new(solved_type)) } Boolean(val) => SolvedType::Boolean(val), - Variable(_var) => { - panic!("TODO handle translate variable to SolvedType (wildcard or rigid)"); + Variable(var) => { + let content = solved_subs.inner().get_without_compacting(var).content; + Self::from_content(solved_subs.inner(), content) } } } @@ -149,16 +150,17 @@ impl SolvedType { use crate::subs::Content::*; match content { - FlexVar(_) => SolvedType::Wildcard, + FlexVar(None) => SolvedType::Wildcard, + FlexVar(Some(name)) => SolvedType::Rigid(name), RigidVar(name) => SolvedType::Rigid(name), Structure(flat_type) => Self::from_flat_type(subs, flat_type), Alias(symbol, args, var) => { let mut new_args = Vec::with_capacity(args.len()); - for (arg_name, _arg_var) in args { + for (arg_name, arg_var) in args { new_args.push(( arg_name, - Self::from_content(subs, subs.get_without_compacting(var).content), + Self::from_content(subs, subs.get_without_compacting(arg_var).content), )); } @@ -607,7 +609,10 @@ fn solve( debug_assert!({ let failing: Vec<_> = rigid_vars .iter() - .filter(|&var| subs.get_without_compacting(*var).rank != Rank::NONE) + .filter(|&var| { + !subs.redundant(*var) + && subs.get_without_compacting(*var).rank != Rank::NONE + }) .collect(); if !failing.is_empty() { diff --git a/src/subs.rs b/src/subs.rs index 223f38409e..e3aad2ed8d 100644 --- a/src/subs.rs +++ b/src/subs.rs @@ -288,7 +288,7 @@ impl Subs { self.utable.unioned(left, right) } - pub fn redundant(&mut self, var: Variable) -> bool { + pub fn redundant(&self, var: Variable) -> bool { self.utable.is_redirect(var) } diff --git a/tests/fixtures/build/interface_with_deps/Primary.roc b/tests/fixtures/build/interface_with_deps/Primary.roc index 1e8d4f33ad..d55a01b625 100644 --- a/tests/fixtures/build/interface_with_deps/Primary.roc +++ b/tests/fixtures/build/interface_with_deps/Primary.roc @@ -1,6 +1,6 @@ interface Primary exposes [ blah, str ] - imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar } ] + imports [ Dep1, Dep2.{ two, foo }, Dep3.Blah.{ bar }, Result ] blah = {} @@ -17,6 +17,24 @@ identity = \a -> a z : Dep1.Unit z = Unit -w : Dep1.Identity Int -w = Identity 42 +w : Dep1.Identity {} +w = Identity {} +succeed : a -> Dep1.Identity a +succeed = \x -> Identity x + +map = Result.withDefault + +yay : Result.Result e {} +yay = Ok {} + + + +# yay = +# v = Ok "foo" +# +# f = \_ -> {} +# +# v +# |> Result.map f +# |> Result.withDefault {} diff --git a/tests/fixtures/build/interface_with_deps/Result.roc b/tests/fixtures/build/interface_with_deps/Result.roc new file mode 100644 index 0000000000..c7e4e669ee --- /dev/null +++ b/tests/fixtures/build/interface_with_deps/Result.roc @@ -0,0 +1,23 @@ +interface Result + exposes [ Result, withDefault, map, andThen ] + imports [] + +Result e a : [ Ok a, Err e ] + +map : Result e a, (a -> b) -> Result e b +map = \result, f -> + when result is + Ok v -> Ok (f v) + Err e -> Err e + +withDefault : Result x a, a -> a +withDefault = \result, default -> + when result is + Ok v -> v + Err _ -> default + +andThen : Result e a, (a -> Result e b) -> Result e b +andThen = \result, f -> + when result is + Ok v -> f v + Err e -> Err e diff --git a/tests/test_load.rs b/tests/test_load.rs index 8dc4fbe96e..3b2a023724 100644 --- a/tests/test_load.rs +++ b/tests/test_load.rs @@ -159,7 +159,7 @@ mod test_load { .expect("Test ModuleID not found in module_ids"); assert_eq!(expected_name, &InlinableString::from("Primary")); - assert_eq!(def_count, 5); + assert_eq!(def_count, 9); }); } @@ -306,7 +306,10 @@ mod test_load { "alwaysThree" => "* -> Str", "identity" => "a -> a", "z" => "Dep1.Unit", - "w" => "Dep1.Identity Int", + "w" => "Dep1.Identity {}", + "succeed" => "a -> Dep1.Identity a", + "yay" => "Result.Result e {}", + "map" => "Result.Result * a, a -> a", }, ); });