Move solve and uniq tests into other crates

This commit is contained in:
Richard Feldman 2020-03-06 02:39:40 -05:00
parent d8cf402528
commit a2f5f6f9fb
21 changed files with 910 additions and 47 deletions

18
Cargo.lock generated
View file

@ -889,7 +889,7 @@ dependencies = [
"roc_solve", "roc_solve",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
"roc_uniqueness", "roc_uniq",
"target-lexicon", "target-lexicon",
"tokio", "tokio",
"wyhash", "wyhash",
@ -959,7 +959,7 @@ dependencies = [
"roc_parse", "roc_parse",
"roc_region", "roc_region",
"roc_types", "roc_types",
"roc_uniqueness", "roc_uniq",
] ]
[[package]] [[package]]
@ -1031,15 +1031,21 @@ dependencies = [
name = "roc_solve" name = "roc_solve"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo",
"indoc", "indoc",
"maplit", "maplit",
"pretty_assertions", "pretty_assertions",
"quickcheck", "quickcheck",
"quickcheck_macros", "quickcheck_macros",
"roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain",
"roc_module", "roc_module",
"roc_parse",
"roc_problem",
"roc_region", "roc_region",
"roc_solve",
"roc_types", "roc_types",
"roc_unify", "roc_unify",
] ]
@ -1077,9 +1083,10 @@ dependencies = [
] ]
[[package]] [[package]]
name = "roc_uniqueness" name = "roc_uniq"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"bumpalo",
"im", "im",
"im-rc", "im-rc",
"indoc", "indoc",
@ -1087,10 +1094,15 @@ dependencies = [
"pretty_assertions", "pretty_assertions",
"quickcheck", "quickcheck",
"quickcheck_macros", "quickcheck_macros",
"roc_builtins",
"roc_can", "roc_can",
"roc_collections", "roc_collections",
"roc_constrain",
"roc_module", "roc_module",
"roc_parse",
"roc_problem",
"roc_region", "roc_region",
"roc_solve",
"roc_types", "roc_types",
] ]

View file

@ -9,7 +9,7 @@ members = [
"compiler/can", "compiler/can",
"compiler/problem", "compiler/problem",
"compiler/types", "compiler/types",
"compiler/uniqueness", "compiler/uniq",
"compiler/builtins", "compiler/builtins",
"compiler/constrain", "compiler/constrain",
"compiler/unify", "compiler/unify",

View file

@ -14,7 +14,7 @@ roc_types = { path = "./types" }
roc_can = { path = "./can" } roc_can = { path = "./can" }
roc_builtins = { path = "./builtins" } roc_builtins = { path = "./builtins" }
roc_constrain = { path = "./constrain" } roc_constrain = { path = "./constrain" }
roc_uniqueness = { path = "./uniqueness" } roc_uniq = { path = "./uniq" }
roc_unify = { path = "./unify" } roc_unify = { path = "./unify" }
roc_solve = { path = "./solve" } roc_solve = { path = "./solve" }
log = "0.4.8" log = "0.4.8"

View file

@ -55,7 +55,7 @@ fn disjunction(free: VarId, rest: Vec<VarId>) -> SolvedType {
SolvedType::Boolean(SolvedAtom::Variable(free), solved_rest) SolvedType::Boolean(SolvedAtom::Variable(free), solved_rest)
} }
pub fn uniqueness_stdlib() -> StdLib { pub fn uniq_stdlib() -> StdLib {
use crate::std::Mode; use crate::std::Mode;
let types = types(); let types = types();

View file

@ -12,7 +12,7 @@ roc_parse = { path = "../parse" }
roc_types = { path = "../types" } roc_types = { path = "../types" }
roc_can = { path = "../can" } roc_can = { path = "../can" }
roc_builtins = { path = "../builtins" } roc_builtins = { path = "../builtins" }
roc_uniqueness = { path = "../uniqueness" } roc_uniq = { path = "../uniq" }
[dev-dependencies] [dev-dependencies]
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1 "

View file

@ -14,4 +14,4 @@ pub mod builtins;
pub mod expr; pub mod expr;
pub mod module; pub mod module;
pub mod pattern; pub mod pattern;
pub mod uniqueness; pub mod uniq;

View file

@ -29,7 +29,7 @@ pub fn constrain_module(
match mode { match mode {
Standard => constrain_decls(home, decls, send_aliases), Standard => constrain_decls(home, decls, send_aliases),
Uniqueness => crate::uniqueness::constrain_decls(home, decls, send_aliases, var_store), Uniqueness => crate::uniq::constrain_decls(home, decls, send_aliases, var_store),
} }
} }

View file

@ -15,8 +15,8 @@ use roc_types::subs::{VarStore, Variable};
use roc_types::types::AnnotationSource::{self, *}; use roc_types::types::AnnotationSource::{self, *};
use roc_types::types::Type::{self, *}; use roc_types::types::Type::{self, *};
use roc_types::types::{Alias, PReason, Reason}; use roc_types::types::{Alias, PReason, Reason};
use roc_uniqueness::builtins::{attr_type, empty_list_type, list_type, str_type}; use roc_uniq::builtins::{attr_type, empty_list_type, list_type, str_type};
use roc_uniqueness::sharing::{self, Container, FieldAccess, Mark, Usage, VarUsage}; use roc_uniq::sharing::{self, Container, FieldAccess, Mark, Usage, VarUsage};
pub struct Env { pub struct Env {
/// Whenever we encounter a user-defined type variable (a "rigid" var for short), /// Whenever we encounter a user-defined type variable (a "rigid" var for short),

View file

@ -13,8 +13,14 @@ roc_can = { path = "../can" }
roc_unify = { path = "../unify" } roc_unify = { path = "../unify" }
[dev-dependencies] [dev-dependencies]
roc_constrain = { path = "../constrain" }
roc_builtins = { path = "../builtins" }
roc_problem = { path = "../problem" }
roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1 "
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"
quickcheck_macros = "0.8" quickcheck_macros = "0.8"
bumpalo = "2.6"

View file

@ -613,7 +613,7 @@ fn check_for_infinite_type(
) { ) {
let var = loc_var.value; let var = loc_var.value;
let is_uniqueness_infer = match subs.get(var).content { let is_uniq_infer = match subs.get(var).content {
Content::Alias(Symbol::ATTR_ATTR, _, _) => true, Content::Alias(Symbol::ATTR_ATTR, _, _) => true,
_ => false, _ => false,
}; };
@ -625,7 +625,7 @@ fn check_for_infinite_type(
// try to make a tag union recursive, see if that helps // try to make a tag union recursive, see if that helps
match content { match content {
Content::Structure(FlatType::TagUnion(tags, ext_var)) => { Content::Structure(FlatType::TagUnion(tags, ext_var)) => {
if !is_uniqueness_infer { if !is_uniq_infer {
let rec_var = subs.fresh_unnamed_flex_var(); let rec_var = subs.fresh_unnamed_flex_var();
subs.set_rank(rec_var, description.rank); subs.set_rank(rec_var, description.rank);

View file

@ -0,0 +1,426 @@
extern crate bumpalo;
use self::bumpalo::Bump;
use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint;
use roc_can::env::Env;
use roc_can::expected::Expected;
use roc_can::expr::{canonicalize_expr, Expr, Output};
use roc_can::operator;
use roc_can::scope::Scope;
use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet};
use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State};
use roc_problem::can::Problem;
use roc_region::all::{Located, Region};
use roc_solve::solve;
use roc_types::subs::{Content, Subs, VarStore, Variable};
use roc_types::types::Type;
use std::hash::Hash;
use std::path::{Path, PathBuf};
pub fn test_home() -> ModuleId {
ModuleIds::default().get_or_insert(&"Test".into())
}
#[allow(dead_code)]
pub fn infer_expr(
subs: Subs,
problems: &mut Vec<roc_types::types::Problem>,
constraint: &Constraint,
expr_var: Variable,
) -> (Content, Subs) {
let env = solve::Env {
aliases: MutMap::default(),
vars_by_symbol: SendMap::default(),
};
let (solved, _) = solve::run(&env, problems, subs, constraint);
let content = solved.inner().get_without_compacting(expr_var).content;
(content, solved.into_inner())
}
/// 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.
#[allow(dead_code)]
#[cfg(not(debug_assertions))]
#[inline(always)]
pub fn with_larger_debug_stack<F>(run_test: F)
where
F: FnOnce() -> (),
F: Send,
F: 'static,
{
run_test()
}
#[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
}
#[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
let state = State::new(&input, Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
}
#[allow(dead_code)]
pub fn can_expr(expr_str: &str) -> CanExprOut {
can_expr_with(&Bump::new(), test_home(), expr_str)
}
#[allow(dead_code)]
pub fn uniq_expr(
expr_str: &str,
) -> (
Located<Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
) {
let declared_idents: &ImMap<Ident, (Symbol, Region)> = &ImMap::default();
uniq_expr_with(&Bump::new(), expr_str, declared_idents)
}
#[allow(dead_code)]
pub fn uniq_expr_with(
arena: &Bump,
expr_str: &str,
declared_idents: &ImMap<Ident, (Symbol, Region)>,
) -> (
Located<Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
) {
let home = test_home();
let CanExprOut {
loc_expr,
output,
problems,
var_store: old_var_store,
var,
interns,
..
} = can_expr_with(arena, home, expr_str);
// double check
let var_store = VarStore::new(old_var_store.fresh());
let expected2 = Expected::NoExpectation(Type::Variable(var));
let constraint = roc_constrain::uniq::constrain_declaration(
home,
&var_store,
Region::zero(),
&loc_expr,
declared_idents,
expected2,
);
let stdlib = uniq_stdlib();
let types = stdlib.types;
let imports: Vec<_> = types
.iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(*region, *symbol),
solved_type: solved_type,
})
.collect();
// load builtin values
let constraint = constrain_imported_values(imports, constraint, &var_store);
// load builtin types
let mut constraint = load_builtin_aliases(&stdlib.aliases, constraint, &var_store);
constraint.instantiate_aliases(&var_store);
let subs2 = Subs::new(var_store.into());
(
loc_expr, output, problems, subs2, var, constraint, home, interns,
)
}
pub struct CanExprOut {
pub loc_expr: Located<Expr>,
pub output: Output,
pub problems: Vec<Problem>,
pub home: ModuleId,
pub interns: Interns,
pub var_store: VarStore,
pub var: Variable,
pub constraint: Constraint,
}
#[allow(dead_code)]
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
panic!(
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
expr_str, e
)
});
let var_store = VarStore::default();
let var = var_store.fresh();
let expected = Expected::NoExpectation(Type::Variable(var));
let module_ids = ModuleIds::default();
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
// If we did this *during* canonicalization, then each time we
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let loc_expr = operator::desugar_expr(arena, &loc_expr);
let mut scope = Scope::new(home);
let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
let (loc_expr, output) = canonicalize_expr(
&mut env,
&var_store,
&mut scope,
Region::zero(),
&loc_expr.value,
);
let constraint = constrain_expr(
&roc_constrain::expr::Env {
rigids: ImMap::default(),
home,
},
loc_expr.region,
&loc_expr.value,
expected,
);
let types = roc_builtins::std::types();
let imports: Vec<_> = types
.iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(*region, *symbol),
solved_type: solved_type,
})
.collect();
//load builtin values
let constraint = constrain_imported_values(imports, constraint, &var_store);
//load builtin types
let mut constraint =
load_builtin_aliases(&roc_builtins::std::aliases(), constraint, &var_store);
constraint.instantiate_aliases(&var_store);
let mut all_ident_ids = MutMap::default();
// When pretty printing types, we may need the exposed builtins,
// so include them in the Interns we'll ultimately return.
for (module_id, ident_ids) in IdentIds::exposed_builtins(0) {
all_ident_ids.insert(module_id, ident_ids);
}
all_ident_ids.insert(home, env.ident_ids);
let interns = Interns {
module_ids: env.module_ids.clone(),
all_ident_ids,
};
CanExprOut {
loc_expr,
output,
problems: env.problems,
home: env.home,
var_store,
interns,
var,
constraint,
}
}
#[allow(dead_code)]
pub fn mut_map_from_pairs<K, V, I>(pairs: I) -> MutMap<K, V>
where
I: IntoIterator<Item = (K, V)>,
K: Hash + Eq,
{
let mut answer = MutMap::default();
for (key, value) in pairs {
answer.insert(key, value);
}
answer
}
#[allow(dead_code)]
pub fn im_map_from_pairs<K, V, I>(pairs: I) -> ImMap<K, V>
where
I: IntoIterator<Item = (K, V)>,
K: Hash + Eq + Clone,
V: Clone,
{
let mut answer = ImMap::default();
for (key, value) in pairs {
answer.insert(key, value);
}
answer
}
#[allow(dead_code)]
pub fn send_set_from<V, I>(elems: I) -> SendSet<V>
where
I: IntoIterator<Item = V>,
V: Hash + Eq + Clone,
{
let mut answer = SendSet::default();
for elem in elems {
answer.insert(elem);
}
answer
}
#[allow(dead_code)]
pub fn fixtures_dir<'a>() -> PathBuf {
Path::new("tests").join("fixtures").join("build")
}
#[allow(dead_code)]
pub fn builtins_dir<'a>() -> PathBuf {
PathBuf::new().join("builtins")
}
// Check constraints
//
// Keep track of the used (in types or expectations) variables, and the declared variables (in
// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates
// and no variables that are used but not declared are allowed.
//
// There is one exception: the initial variable (that stores the type of the whole expression) is
// never declared, but is used.
#[allow(dead_code)]
pub fn assert_correct_variable_usage(constraint: &Constraint) {
// variables declared in constraint (flex_vars or rigid_vars)
// and variables actually used in constraints
let (declared, used) = variable_usage(constraint);
let used: ImSet<Variable> = used.clone().into();
let mut decl: ImSet<Variable> = declared.rigid_vars.clone().into();
for var in declared.flex_vars.clone() {
decl.insert(var);
}
let diff = used.clone().relative_complement(decl);
// NOTE: this checks whether we're using variables that are not declared. For recursive type
// definitions, their rigid types are declared twice, which is correct!
if !diff.is_empty() {
println!("VARIABLE USAGE PROBLEM");
println!("used: {:?}", &used);
println!("rigids: {:?}", &declared.rigid_vars);
println!("flexs: {:?}", &declared.flex_vars);
println!("difference: {:?}", &diff);
panic!("variable usage problem (see stdout for details)");
}
}
#[derive(Default)]
pub struct SeenVariables {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
}
pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
let mut declared = SeenVariables::default();
let mut used = ImSet::default();
variable_usage_help(con, &mut declared, &mut used);
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
let mut used_vec: Vec<Variable> = used.into_iter().collect();
used_vec.sort();
declared.rigid_vars.sort();
declared.flex_vars.sort();
(declared, used_vec)
}
fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet<Variable>) {
use Constraint::*;
match con {
True | SaveTheEnvironment => (),
Eq(tipe, expectation, _) => {
for v in tipe.variables() {
used.insert(v);
}
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Lookup(_, expectation, _) => {
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Pattern(_, _, tipe, pexpectation) => {
for v in tipe.variables() {
used.insert(v);
}
for v in pexpectation.get_type_ref().variables() {
used.insert(v);
}
}
Let(letcon) => {
declared.rigid_vars.extend(letcon.rigid_vars.clone());
declared.flex_vars.extend(letcon.flex_vars.clone());
variable_usage_help(&letcon.defs_constraint, declared, used);
variable_usage_help(&letcon.ret_constraint, declared, used);
}
And(constraints) => {
for sub in constraints {
variable_usage_help(sub, declared, used);
}
}
}
}

View file

@ -4,12 +4,11 @@ extern crate pretty_assertions;
extern crate indoc; extern crate indoc;
extern crate bumpalo; extern crate bumpalo;
extern crate roc;
mod helpers; mod helpers;
#[cfg(test)] #[cfg(test)]
mod test_infer { mod test_solve {
use crate::helpers::{assert_correct_variable_usage, can_expr, infer_expr, CanExprOut}; use crate::helpers::{assert_correct_variable_usage, can_expr, infer_expr, CanExprOut};
use roc_types::pretty_print::{content_to_string, name_all_type_vars}; use roc_types::pretty_print::{content_to_string, name_all_type_vars};
use roc_types::subs::Subs; use roc_types::subs::Subs;

View file

@ -4,12 +4,11 @@ extern crate pretty_assertions;
extern crate indoc; extern crate indoc;
extern crate bumpalo; extern crate bumpalo;
extern crate roc;
mod helpers; mod helpers;
#[cfg(test)] #[cfg(test)]
mod test_infer_uniq { mod test_uniq_solve {
use crate::helpers::{ use crate::helpers::{
assert_correct_variable_usage, infer_expr, uniq_expr, with_larger_debug_stack, assert_correct_variable_usage, infer_expr, uniq_expr, with_larger_debug_stack,
}; };

View file

@ -1,17 +1,16 @@
extern crate bumpalo; extern crate bumpalo;
use self::bumpalo::Bump; use self::bumpalo::Bump;
use roc_builtins::unique; use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint; use roc_can::constraint::Constraint;
use roc_can::env::Env; use roc_can::env::Env;
use roc_can::expected::Expected; use roc_can::expected::Expected;
use roc_can::expr::Output; use roc_can::expr::{canonicalize_expr, Expr, Output};
use roc_can::expr::{canonicalize_expr, Expr};
use roc_can::operator; use roc_can::operator;
use roc_can::scope::Scope; use roc_can::scope::Scope;
use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet}; use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet};
use roc_constrain::expr::constrain_expr; use roc_constrain::expr::constrain_expr;
use roc_constrain::module::Import; use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
use roc_module::ident::Ident; use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol}; use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting}; use roc_parse::ast::{self, Attempting};
@ -156,7 +155,7 @@ pub fn uniq_expr_with(
let var_store = VarStore::new(old_var_store.fresh()); let var_store = VarStore::new(old_var_store.fresh());
let expected2 = Expected::NoExpectation(Type::Variable(var)); let expected2 = Expected::NoExpectation(Type::Variable(var));
let constraint = roc_constrain::uniqueness::constrain_declaration( let constraint = roc_constrain::uniq::constrain_declaration(
home, home,
&var_store, &var_store,
Region::zero(), Region::zero(),
@ -165,7 +164,7 @@ pub fn uniq_expr_with(
expected2, expected2,
); );
let stdlib = unique::uniqueness_stdlib(); let stdlib = uniq_stdlib();
let types = stdlib.types; let types = stdlib.types;
let imports: Vec<_> = types let imports: Vec<_> = types
@ -180,11 +179,10 @@ pub fn uniq_expr_with(
// TODO what to do with those rigids? // TODO what to do with those rigids?
let (_introduced_rigids, constraint) = let (_introduced_rigids, constraint) =
roc_constrain::module::constrain_imported_values(imports, constraint, &var_store); constrain_imported_values(imports, constraint, &var_store);
// load builtin types // load builtin types
let mut constraint = let mut constraint = load_builtin_aliases(&stdlib.aliases, constraint, &var_store);
roc_constrain::module::load_builtin_aliases(&stdlib.aliases, constraint, &var_store);
constraint.instantiate_aliases(&var_store); constraint.instantiate_aliases(&var_store);
@ -262,7 +260,7 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
//load builtin values //load builtin values
let (_introduced_rigids, constraint) = let (_introduced_rigids, constraint) =
roc_constrain::module::constrain_imported_values(imports, constraint, &var_store); constrain_imported_values(imports, constraint, &var_store);
// TODO determine what to do with those rigids // TODO determine what to do with those rigids
// for var in introduced_rigids { // for var in introduced_rigids {
@ -270,11 +268,8 @@ pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut
// } // }
//load builtin types //load builtin types
let mut constraint = roc_constrain::module::load_builtin_aliases( let mut constraint =
&roc_builtins::std::aliases(), load_builtin_aliases(&roc_builtins::std::aliases(), constraint, &var_store);
constraint,
&var_store,
);
constraint.instantiate_aliases(&var_store); constraint.instantiate_aliases(&var_store);

View file

@ -12,7 +12,7 @@ extern crate roc_module;
mod helpers; mod helpers;
#[cfg(test)] #[cfg(test)]
mod test_uniqueness_load { mod test_uniq_load {
use crate::helpers::fixtures_dir; use crate::helpers::fixtures_dir;
use inlinable_string::InlinableString; use inlinable_string::InlinableString;
use roc::load::{load, LoadedModule}; use roc::load::{load, LoadedModule};
@ -45,13 +45,7 @@ mod test_uniqueness_load {
) -> LoadedModule { ) -> LoadedModule {
let src_dir = fixtures_dir().join(dir_name); let src_dir = fixtures_dir().join(dir_name);
let filename = src_dir.join(format!("{}.roc", module_name)); let filename = src_dir.join(format!("{}.roc", module_name));
let loaded = load( let loaded = load(&unique::uniq_stdlib(), src_dir, filename, subs_by_module).await;
&unique::uniqueness_stdlib(),
src_dir,
filename,
subs_by_module,
)
.await;
let loaded_module = loaded.expect("Test module failed to load"); let loaded_module = loaded.expect("Test module failed to load");
assert_eq!(loaded_module.can_problems, Vec::new()); assert_eq!(loaded_module.can_problems, Vec::new());

View file

@ -1,5 +1,5 @@
[package] [package]
name = "roc_uniqueness" name = "roc_uniq"
version = "0.1.0" version = "0.1.0"
authors = ["Richard Feldman <oss@rtfeldman.com>"] authors = ["Richard Feldman <oss@rtfeldman.com>"]
edition = "2018" edition = "2018"
@ -14,8 +14,14 @@ im = "14" # im and im-rc should always have the same version!
im-rc = "14" # im and im-rc should always have the same version! im-rc = "14" # im and im-rc should always have the same version!
[dev-dependencies] [dev-dependencies]
roc_constrain = { path = "../constrain" }
roc_builtins = { path = "../builtins" }
roc_problem = { path = "../problem" }
roc_parse = { path = "../parse" }
roc_solve = { path = "../solve" }
pretty_assertions = "0.5.1 " pretty_assertions = "0.5.1 "
maplit = "1.0.1" maplit = "1.0.1"
indoc = "0.3.3" indoc = "0.3.3"
quickcheck = "0.8" quickcheck = "0.8"
quickcheck_macros = "0.8" quickcheck_macros = "0.8"
bumpalo = "2.6"

View file

@ -0,0 +1,426 @@
extern crate bumpalo;
use self::bumpalo::Bump;
use roc_builtins::unique::uniq_stdlib;
use roc_can::constraint::Constraint;
use roc_can::env::Env;
use roc_can::expected::Expected;
use roc_can::expr::{canonicalize_expr, Expr, Output};
use roc_can::operator;
use roc_can::scope::Scope;
use roc_collections::all::{ImMap, ImSet, MutMap, SendMap, SendSet};
use roc_constrain::expr::constrain_expr;
use roc_constrain::module::{constrain_imported_values, load_builtin_aliases, Import};
use roc_module::ident::Ident;
use roc_module::symbol::{IdentIds, Interns, ModuleId, ModuleIds, Symbol};
use roc_parse::ast::{self, Attempting};
use roc_parse::blankspace::space0_before;
use roc_parse::parser::{loc, Fail, Parser, State};
use roc_problem::can::Problem;
use roc_region::all::{Located, Region};
use roc_solve::solve;
use roc_types::subs::{Content, Subs, VarStore, Variable};
use roc_types::types::Type;
use std::hash::Hash;
use std::path::{Path, PathBuf};
pub fn test_home() -> ModuleId {
ModuleIds::default().get_or_insert(&"Test".into())
}
#[allow(dead_code)]
pub fn infer_expr(
subs: Subs,
problems: &mut Vec<roc_types::types::Problem>,
constraint: &Constraint,
expr_var: Variable,
) -> (Content, Subs) {
let env = solve::Env {
aliases: MutMap::default(),
vars_by_symbol: SendMap::default(),
};
let (solved, _) = solve::run(&env, problems, subs, constraint);
let content = solved.inner().get_without_compacting(expr_var).content;
(content, solved.into_inner())
}
/// 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.
#[allow(dead_code)]
#[cfg(not(debug_assertions))]
#[inline(always)]
pub fn with_larger_debug_stack<F>(run_test: F)
where
F: FnOnce() -> (),
F: Send,
F: 'static,
{
run_test()
}
#[allow(dead_code)]
pub fn parse_with<'a>(arena: &'a Bump, input: &'a str) -> Result<ast::Expr<'a>, Fail> {
parse_loc_with(arena, input).map(|loc_expr| loc_expr.value)
}
#[allow(dead_code)]
pub fn parse_loc_with<'a>(arena: &'a Bump, input: &'a str) -> Result<Located<ast::Expr<'a>>, Fail> {
let state = State::new(&input, Attempting::Module);
let parser = space0_before(loc(roc_parse::expr::expr(0)), 0);
let answer = parser.parse(&arena, state);
answer
.map(|(loc_expr, _)| loc_expr)
.map_err(|(fail, _)| fail)
}
#[allow(dead_code)]
pub fn can_expr(expr_str: &str) -> CanExprOut {
can_expr_with(&Bump::new(), test_home(), expr_str)
}
#[allow(dead_code)]
pub fn uniq_expr(
expr_str: &str,
) -> (
Located<Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
) {
let declared_idents: &ImMap<Ident, (Symbol, Region)> = &ImMap::default();
uniq_expr_with(&Bump::new(), expr_str, declared_idents)
}
#[allow(dead_code)]
pub fn uniq_expr_with(
arena: &Bump,
expr_str: &str,
declared_idents: &ImMap<Ident, (Symbol, Region)>,
) -> (
Located<Expr>,
Output,
Vec<Problem>,
Subs,
Variable,
Constraint,
ModuleId,
Interns,
) {
let home = test_home();
let CanExprOut {
loc_expr,
output,
problems,
var_store: old_var_store,
var,
interns,
..
} = can_expr_with(arena, home, expr_str);
// double check
let var_store = VarStore::new(old_var_store.fresh());
let expected2 = Expected::NoExpectation(Type::Variable(var));
let constraint = roc_constrain::uniq::constrain_declaration(
home,
&var_store,
Region::zero(),
&loc_expr,
declared_idents,
expected2,
);
let stdlib = uniq_stdlib();
let types = stdlib.types;
let imports: Vec<_> = types
.iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(*region, *symbol),
solved_type: solved_type,
})
.collect();
// load builtin values
let constraint = constrain_imported_values(imports, constraint, &var_store);
// load builtin types
let mut constraint = load_builtin_aliases(&stdlib.aliases, constraint, &var_store);
constraint.instantiate_aliases(&var_store);
let subs2 = Subs::new(var_store.into());
(
loc_expr, output, problems, subs2, var, constraint, home, interns,
)
}
pub struct CanExprOut {
pub loc_expr: Located<Expr>,
pub output: Output,
pub problems: Vec<Problem>,
pub home: ModuleId,
pub interns: Interns,
pub var_store: VarStore,
pub var: Variable,
pub constraint: Constraint,
}
#[allow(dead_code)]
pub fn can_expr_with(arena: &Bump, home: ModuleId, expr_str: &str) -> CanExprOut {
let loc_expr = parse_loc_with(&arena, expr_str).unwrap_or_else(|e| {
panic!(
"can_expr_with() got a parse error when attempting to canonicalize:\n\n{:?} {:?}",
expr_str, e
)
});
let var_store = VarStore::default();
let var = var_store.fresh();
let expected = Expected::NoExpectation(Type::Variable(var));
let module_ids = ModuleIds::default();
// Desugar operators (convert them to Apply calls, taking into account
// operator precedence and associativity rules), before doing other canonicalization.
//
// If we did this *during* canonicalization, then each time we
// visited a BinOp node we'd recursively try to apply this to each of its nested
// operators, and then again on *their* nested operators, ultimately applying the
// rules multiple times unnecessarily.
let loc_expr = operator::desugar_expr(arena, &loc_expr);
let mut scope = Scope::new(home);
let dep_idents = IdentIds::exposed_builtins(0);
let mut env = Env::new(home, dep_idents, &module_ids, IdentIds::default());
let (loc_expr, output) = canonicalize_expr(
&mut env,
&var_store,
&mut scope,
Region::zero(),
&loc_expr.value,
);
let constraint = constrain_expr(
&roc_constrain::expr::Env {
rigids: ImMap::default(),
home,
},
loc_expr.region,
&loc_expr.value,
expected,
);
let types = roc_builtins::std::types();
let imports: Vec<_> = types
.iter()
.map(|(symbol, (solved_type, region))| Import {
loc_symbol: Located::at(*region, *symbol),
solved_type: solved_type,
})
.collect();
//load builtin values
let constraint = constrain_imported_values(imports, constraint, &var_store);
//load builtin types
let mut constraint =
load_builtin_aliases(&roc_builtins::std::aliases(), constraint, &var_store);
constraint.instantiate_aliases(&var_store);
let mut all_ident_ids = MutMap::default();
// When pretty printing types, we may need the exposed builtins,
// so include them in the Interns we'll ultimately return.
for (module_id, ident_ids) in IdentIds::exposed_builtins(0) {
all_ident_ids.insert(module_id, ident_ids);
}
all_ident_ids.insert(home, env.ident_ids);
let interns = Interns {
module_ids: env.module_ids.clone(),
all_ident_ids,
};
CanExprOut {
loc_expr,
output,
problems: env.problems,
home: env.home,
var_store,
interns,
var,
constraint,
}
}
#[allow(dead_code)]
pub fn mut_map_from_pairs<K, V, I>(pairs: I) -> MutMap<K, V>
where
I: IntoIterator<Item = (K, V)>,
K: Hash + Eq,
{
let mut answer = MutMap::default();
for (key, value) in pairs {
answer.insert(key, value);
}
answer
}
#[allow(dead_code)]
pub fn im_map_from_pairs<K, V, I>(pairs: I) -> ImMap<K, V>
where
I: IntoIterator<Item = (K, V)>,
K: Hash + Eq + Clone,
V: Clone,
{
let mut answer = ImMap::default();
for (key, value) in pairs {
answer.insert(key, value);
}
answer
}
#[allow(dead_code)]
pub fn send_set_from<V, I>(elems: I) -> SendSet<V>
where
I: IntoIterator<Item = V>,
V: Hash + Eq + Clone,
{
let mut answer = SendSet::default();
for elem in elems {
answer.insert(elem);
}
answer
}
#[allow(dead_code)]
pub fn fixtures_dir<'a>() -> PathBuf {
Path::new("tests").join("fixtures").join("build")
}
#[allow(dead_code)]
pub fn builtins_dir<'a>() -> PathBuf {
PathBuf::new().join("builtins")
}
// Check constraints
//
// Keep track of the used (in types or expectations) variables, and the declared variables (in
// flex_vars or rigid_vars fields of LetConstraint. These roc_collections should match: no duplicates
// and no variables that are used but not declared are allowed.
//
// There is one exception: the initial variable (that stores the type of the whole expression) is
// never declared, but is used.
#[allow(dead_code)]
pub fn assert_correct_variable_usage(constraint: &Constraint) {
// variables declared in constraint (flex_vars or rigid_vars)
// and variables actually used in constraints
let (declared, used) = variable_usage(constraint);
let used: ImSet<Variable> = used.clone().into();
let mut decl: ImSet<Variable> = declared.rigid_vars.clone().into();
for var in declared.flex_vars.clone() {
decl.insert(var);
}
let diff = used.clone().relative_complement(decl);
// NOTE: this checks whether we're using variables that are not declared. For recursive type
// definitions, their rigid types are declared twice, which is correct!
if !diff.is_empty() {
println!("VARIABLE USAGE PROBLEM");
println!("used: {:?}", &used);
println!("rigids: {:?}", &declared.rigid_vars);
println!("flexs: {:?}", &declared.flex_vars);
println!("difference: {:?}", &diff);
panic!("variable usage problem (see stdout for details)");
}
}
#[derive(Default)]
pub struct SeenVariables {
pub rigid_vars: Vec<Variable>,
pub flex_vars: Vec<Variable>,
}
pub fn variable_usage(con: &Constraint) -> (SeenVariables, Vec<Variable>) {
let mut declared = SeenVariables::default();
let mut used = ImSet::default();
variable_usage_help(con, &mut declared, &mut used);
used.remove(unsafe { &Variable::unsafe_test_debug_variable(1) });
let mut used_vec: Vec<Variable> = used.into_iter().collect();
used_vec.sort();
declared.rigid_vars.sort();
declared.flex_vars.sort();
(declared, used_vec)
}
fn variable_usage_help(con: &Constraint, declared: &mut SeenVariables, used: &mut ImSet<Variable>) {
use Constraint::*;
match con {
True | SaveTheEnvironment => (),
Eq(tipe, expectation, _) => {
for v in tipe.variables() {
used.insert(v);
}
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Lookup(_, expectation, _) => {
for v in expectation.get_type_ref().variables() {
used.insert(v);
}
}
Pattern(_, _, tipe, pexpectation) => {
for v in tipe.variables() {
used.insert(v);
}
for v in pexpectation.get_type_ref().variables() {
used.insert(v);
}
}
Let(letcon) => {
declared.rigid_vars.extend(letcon.rigid_vars.clone());
declared.flex_vars.extend(letcon.flex_vars.clone());
variable_usage_help(&letcon.defs_constraint, declared, used);
variable_usage_help(&letcon.ret_constraint, declared, used);
}
And(constraints) => {
for sub in constraints {
variable_usage_help(sub, declared, used);
}
}
}
}

View file

@ -6,9 +6,9 @@ extern crate pretty_assertions;
extern crate indoc; extern crate indoc;
extern crate bumpalo; extern crate bumpalo;
extern crate roc;
extern crate roc_collections; extern crate roc_collections;
extern crate roc_module; extern crate roc_module;
extern crate roc_uniq;
mod helpers; mod helpers;
@ -18,10 +18,10 @@ mod test_usage_analysis {
use roc_collections::all::{ImMap, ImSet}; use roc_collections::all::{ImMap, ImSet};
use roc_module::ident::Lowercase; use roc_module::ident::Lowercase;
use roc_module::symbol::{Interns, Symbol}; use roc_module::symbol::{Interns, Symbol};
use roc_uniqueness::sharing; use roc_uniq::sharing;
use roc_uniqueness::sharing::FieldAccess; use roc_uniq::sharing::FieldAccess;
use roc_uniqueness::sharing::VarUsage; use roc_uniq::sharing::VarUsage;
use roc_uniqueness::sharing::{Container, Mark, Usage}; use roc_uniq::sharing::{Container, Mark, Usage};
use Container::*; use Container::*;
use Mark::*; use Mark::*;
@ -196,7 +196,7 @@ mod test_usage_analysis {
loc_expr, interns, .. loc_expr, interns, ..
} = can_expr(src); } = can_expr(src);
use roc_uniqueness::sharing::annotate_usage; use roc_uniq::sharing::annotate_usage;
let mut usage = VarUsage::default(); let mut usage = VarUsage::default();
annotate_usage(&loc_expr.value, &mut usage); annotate_usage(&loc_expr.value, &mut usage);