mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-03 08:34:33 +00:00
Merge remote-tracking branch 'origin/trunk' into improve-errors
This commit is contained in:
commit
2f61455061
43 changed files with 3015 additions and 1675 deletions
347
src/solve.rs
347
src/solve.rs
|
@ -32,6 +32,10 @@ impl Pools {
|
|||
Pools(pools)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, rank: Rank) -> &mut Vec<Variable> {
|
||||
self.0
|
||||
.get_mut(rank.into_usize())
|
||||
|
@ -69,9 +73,9 @@ pub fn run(
|
|||
let mut pools = Pools::default();
|
||||
let state = State {
|
||||
vars_by_symbol: vars_by_symbol.clone(),
|
||||
mark: Mark::none().next(),
|
||||
mark: Mark::NONE.next(),
|
||||
};
|
||||
let rank = Rank::outermost();
|
||||
let rank = Rank::toplevel();
|
||||
|
||||
solve(
|
||||
vars_by_symbol,
|
||||
|
@ -96,11 +100,11 @@ fn solve(
|
|||
match constraint {
|
||||
True => state,
|
||||
Eq(typ, expected_type, _region) => {
|
||||
// TODO use region?
|
||||
let actual = type_to_var(subs, typ.clone());
|
||||
let expected = type_to_var(subs, expected_type.clone().get_type());
|
||||
let actual = type_to_var(subs, rank, pools, typ);
|
||||
let expected = type_to_var(subs, rank, pools, expected_type.get_type_ref());
|
||||
let Unified { vars, mismatches } = unify(subs, actual, expected);
|
||||
|
||||
// TODO use region when reporting a problem
|
||||
problems.extend(mismatches);
|
||||
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
@ -108,18 +112,36 @@ fn solve(
|
|||
state
|
||||
}
|
||||
Lookup(symbol, expected_type, _region) => {
|
||||
// TODO use region?
|
||||
let actual = subs.copy_var(*vars_by_symbol.get(&symbol).unwrap_or_else(|| {
|
||||
let var = *vars_by_symbol.get(&symbol).unwrap_or_else(|| {
|
||||
// TODO Instead of panicking, solve this as True and record
|
||||
// a Problem ("module Foo does not expose `bar`") for later.
|
||||
panic!(
|
||||
"Could not find symbol {:?} in vars_by_symbol {:?}",
|
||||
symbol, vars_by_symbol
|
||||
)
|
||||
}));
|
||||
let expected = type_to_var(subs, expected_type.clone().get_type());
|
||||
});
|
||||
|
||||
// Deep copy the vars associated with this symbol before unifying them.
|
||||
// Otherwise, suppose we have this:
|
||||
//
|
||||
// identity = \a -> a
|
||||
//
|
||||
// x = identity 5
|
||||
//
|
||||
// When we call (identity 5), it's important that we not unify
|
||||
// on identity's original vars. If we do, the type of `identity` will be
|
||||
// mutated to be `Int -> Int` instead of `a -> `, which would be incorrect;
|
||||
// the type of `identity` is more general than that!
|
||||
//
|
||||
// Instead, we want to unify on a *copy* of its vars. If the copy unifies
|
||||
// successfully (in this case, to `Int -> Int`), we can use that to
|
||||
// infer the type of this lookup (in this case, `Int`) without ever
|
||||
// having mutated the original.
|
||||
let actual = var; // TODO deep copy this var
|
||||
let expected = type_to_var(subs, rank, pools, expected_type.get_type_ref());
|
||||
let Unified { vars, mismatches } = unify(subs, actual, expected);
|
||||
|
||||
// TODO use region when reporting a problem
|
||||
problems.extend(mismatches);
|
||||
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
@ -144,11 +166,11 @@ fn solve(
|
|||
state
|
||||
}
|
||||
Pattern(_region, _category, typ, expected) => {
|
||||
// TODO use region?
|
||||
let actual = type_to_var(subs, typ.clone());
|
||||
let expected = type_to_var(subs, expected.clone().get_type());
|
||||
let actual = type_to_var(subs, rank, pools, typ);
|
||||
let expected = type_to_var(subs, rank, pools, expected.get_type_ref());
|
||||
let Unified { vars, mismatches } = unify(subs, actual, expected);
|
||||
|
||||
// TODO use region when reporting a problem
|
||||
problems.extend(mismatches);
|
||||
|
||||
introduce(subs, rank, pools, &vars);
|
||||
|
@ -157,7 +179,7 @@ fn solve(
|
|||
}
|
||||
Let(let_con) => {
|
||||
match &let_con.ret_constraint {
|
||||
True => {
|
||||
True if let_con.rigid_vars.is_empty() => {
|
||||
introduce(subs, rank, pools, &let_con.flex_vars);
|
||||
|
||||
// If the return expression is guaranteed to solve,
|
||||
|
@ -172,37 +194,24 @@ fn solve(
|
|||
&let_con.defs_constraint,
|
||||
)
|
||||
}
|
||||
ret_con => {
|
||||
let rigid_vars = &let_con.rigid_vars;
|
||||
let flex_vars = &let_con.flex_vars;
|
||||
ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => {
|
||||
let state = solve(
|
||||
vars_by_symbol,
|
||||
state,
|
||||
rank,
|
||||
pools,
|
||||
problems,
|
||||
subs,
|
||||
&let_con.defs_constraint,
|
||||
);
|
||||
|
||||
// work in the next pool to localize header
|
||||
let next_rank = rank.next();
|
||||
|
||||
let mut next_pools = pools.clone();
|
||||
|
||||
// introduce variables
|
||||
for &var in rigid_vars.iter() {
|
||||
subs.set_rank(var, next_rank);
|
||||
}
|
||||
|
||||
for &var in flex_vars.iter() {
|
||||
subs.set_rank(var, next_rank);
|
||||
}
|
||||
|
||||
let pool: &mut Vec<Variable> = next_pools.get_mut(next_rank);
|
||||
|
||||
pool.reserve(rigid_vars.len() + flex_vars.len());
|
||||
pool.extend(rigid_vars.iter());
|
||||
pool.extend(flex_vars.iter());
|
||||
|
||||
// Add a variable for each assignment to the vars_by_symbol.
|
||||
let mut locals = ImMap::default();
|
||||
// Add a variable for each def to new_vars_by_env.
|
||||
let mut local_def_vars = ImMap::default();
|
||||
|
||||
for (symbol, loc_type) in let_con.def_types.iter() {
|
||||
let var = type_to_var(subs, loc_type.value.clone());
|
||||
let var = type_to_var(subs, rank, pools, &loc_type.value);
|
||||
|
||||
locals.insert(
|
||||
local_def_vars.insert(
|
||||
symbol.clone(),
|
||||
Located {
|
||||
value: var,
|
||||
|
@ -211,75 +220,151 @@ fn solve(
|
|||
);
|
||||
}
|
||||
|
||||
// run solver in next pool
|
||||
|
||||
// Solve the assignments' constraints first.
|
||||
let new_state = solve(
|
||||
vars_by_symbol,
|
||||
state,
|
||||
next_rank,
|
||||
&mut next_pools,
|
||||
problems,
|
||||
subs,
|
||||
&let_con.defs_constraint,
|
||||
);
|
||||
let young_mark = new_state.mark;
|
||||
let visit_mark = young_mark.next();
|
||||
let final_mark = visit_mark.next();
|
||||
|
||||
// pop pool
|
||||
generalize(subs, young_mark, visit_mark, next_rank, &mut next_pools);
|
||||
|
||||
next_pools.get_mut(next_rank).clear();
|
||||
|
||||
// check that things went well
|
||||
debug_assert!(rigid_vars
|
||||
.iter()
|
||||
.all(|&var| subs.get_without_compacting(var).rank == Rank::none()));
|
||||
|
||||
let mut new_vars_by_symbol = vars_by_symbol.clone();
|
||||
|
||||
for (symbol, loc_var) in locals.iter() {
|
||||
new_vars_by_symbol.insert(symbol.clone(), loc_var.value);
|
||||
for (symbol, loc_var) in local_def_vars.iter() {
|
||||
if !new_vars_by_symbol.contains_key(&symbol) {
|
||||
new_vars_by_symbol.insert(symbol.clone(), loc_var.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this vars_by_symbol is the one returned by the
|
||||
// previous call to solve()
|
||||
let temp_state = State {
|
||||
vars_by_symbol: new_state.vars_by_symbol,
|
||||
mark: final_mark,
|
||||
};
|
||||
|
||||
// Now solve the body, using the new vars_by_symbol which includes
|
||||
// the assignments' name-to-variable mappings.
|
||||
let new_state = solve(
|
||||
&new_vars_by_symbol,
|
||||
temp_state,
|
||||
state,
|
||||
rank,
|
||||
&mut next_pools,
|
||||
pools,
|
||||
problems,
|
||||
subs,
|
||||
&ret_con,
|
||||
ret_con,
|
||||
);
|
||||
|
||||
for (symbol, loc_var) in locals {
|
||||
for (symbol, loc_var) in local_def_vars {
|
||||
check_for_infinite_type(subs, problems, symbol, loc_var);
|
||||
}
|
||||
|
||||
new_state
|
||||
}
|
||||
ret_con => {
|
||||
let rigid_vars = &let_con.rigid_vars;
|
||||
let flex_vars = &let_con.flex_vars;
|
||||
|
||||
// work in the next pool to localize header
|
||||
let next_rank = rank.next();
|
||||
|
||||
// introduce variables
|
||||
for &var in rigid_vars.iter().chain(flex_vars.iter()) {
|
||||
subs.set_rank(var, next_rank);
|
||||
}
|
||||
|
||||
let work_in_next_pools = |next_pools: &mut Pools| {
|
||||
let pool: &mut Vec<Variable> = next_pools.get_mut(next_rank);
|
||||
|
||||
// Replace the contents of this pool with rigid_vars and flex_vars
|
||||
pool.clear();
|
||||
pool.reserve(rigid_vars.len() + flex_vars.len());
|
||||
pool.extend(rigid_vars.iter());
|
||||
pool.extend(flex_vars.iter());
|
||||
|
||||
// Add a variable for each def to local_def_vars.
|
||||
let mut local_def_vars = ImMap::default();
|
||||
|
||||
for (symbol, loc_type) in let_con.def_types.iter() {
|
||||
let def_type = loc_type.value.clone();
|
||||
let var = type_to_var(subs, next_rank, next_pools, &def_type);
|
||||
|
||||
local_def_vars.insert(
|
||||
symbol.clone(),
|
||||
Located {
|
||||
value: var,
|
||||
region: loc_type.region,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// run solver in next pool
|
||||
|
||||
// Solve the assignments' constraints first.
|
||||
let new_state = solve(
|
||||
vars_by_symbol,
|
||||
state,
|
||||
next_rank,
|
||||
next_pools,
|
||||
problems,
|
||||
subs,
|
||||
&let_con.defs_constraint,
|
||||
);
|
||||
let young_mark = new_state.mark;
|
||||
let visit_mark = young_mark.next();
|
||||
let final_mark = visit_mark.next();
|
||||
|
||||
// pop pool
|
||||
generalize(subs, young_mark, visit_mark, next_rank, next_pools);
|
||||
|
||||
next_pools.get_mut(next_rank).clear();
|
||||
|
||||
// check that things went well
|
||||
debug_assert!(rigid_vars
|
||||
.iter()
|
||||
.all(|&var| subs.get_without_compacting(var).rank == Rank::NONE));
|
||||
|
||||
let mut new_vars_by_symbol = vars_by_symbol.clone();
|
||||
|
||||
for (symbol, loc_var) in local_def_vars.iter() {
|
||||
if !new_vars_by_symbol.contains_key(&symbol) {
|
||||
new_vars_by_symbol.insert(symbol.clone(), loc_var.value);
|
||||
}
|
||||
}
|
||||
|
||||
// Note that this vars_by_symbol is the one returned by the
|
||||
// previous call to solve()
|
||||
let temp_state = State {
|
||||
vars_by_symbol: new_state.vars_by_symbol,
|
||||
mark: final_mark,
|
||||
};
|
||||
|
||||
// Now solve the body, using the new vars_by_symbol which includes
|
||||
// the assignments' name-to-variable mappings.
|
||||
let new_state = solve(
|
||||
&new_vars_by_symbol,
|
||||
temp_state,
|
||||
rank,
|
||||
next_pools,
|
||||
problems,
|
||||
subs,
|
||||
&ret_con,
|
||||
);
|
||||
|
||||
for (symbol, loc_var) in local_def_vars {
|
||||
check_for_infinite_type(subs, problems, symbol, loc_var);
|
||||
}
|
||||
|
||||
new_state
|
||||
};
|
||||
|
||||
if next_rank.into_usize() < pools.len() {
|
||||
work_in_next_pools(pools)
|
||||
} else {
|
||||
work_in_next_pools(&mut pools.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn type_to_var(subs: &mut Subs, typ: Type) -> Variable {
|
||||
type_to_variable(subs, &ImMap::default(), typ)
|
||||
fn type_to_var(subs: &mut Subs, rank: Rank, pools: &mut Pools, typ: &Type) -> Variable {
|
||||
type_to_variable(subs, rank, pools, &ImMap::default(), typ)
|
||||
}
|
||||
|
||||
fn type_to_variable(subs: &mut Subs, aliases: &ImMap<Lowercase, Variable>, typ: Type) -> Variable {
|
||||
fn type_to_variable(
|
||||
subs: &mut Subs,
|
||||
rank: Rank,
|
||||
pools: &mut Pools,
|
||||
aliases: &ImMap<Lowercase, Variable>,
|
||||
typ: &Type,
|
||||
) -> Variable {
|
||||
match typ {
|
||||
Variable(var) => var,
|
||||
Variable(var) => *var,
|
||||
Apply {
|
||||
module_name,
|
||||
name,
|
||||
|
@ -288,67 +373,70 @@ fn type_to_variable(subs: &mut Subs, aliases: &ImMap<Lowercase, Variable>, typ:
|
|||
let mut arg_vars = Vec::with_capacity(args.len());
|
||||
|
||||
for arg in args {
|
||||
arg_vars.push(type_to_variable(subs, aliases, arg.clone()))
|
||||
arg_vars.push(type_to_variable(subs, rank, pools, aliases, arg))
|
||||
}
|
||||
|
||||
let flat_type = FlatType::Apply {
|
||||
module_name,
|
||||
name,
|
||||
module_name: module_name.clone(),
|
||||
name: name.clone(),
|
||||
args: arg_vars,
|
||||
};
|
||||
let content = Content::Structure(flat_type);
|
||||
|
||||
subs.fresh(Descriptor::from(content))
|
||||
register(subs, rank, pools, content)
|
||||
}
|
||||
EmptyRec => {
|
||||
let content = Content::Structure(FlatType::EmptyRecord);
|
||||
|
||||
subs.fresh(Descriptor::from(content))
|
||||
register(subs, rank, pools, content)
|
||||
}
|
||||
Function(args, ret_type) => {
|
||||
let mut arg_vars = Vec::with_capacity(args.len());
|
||||
|
||||
for arg in args {
|
||||
arg_vars.push(type_to_variable(subs, aliases, arg.clone()))
|
||||
arg_vars.push(type_to_variable(subs, rank, pools, aliases, arg))
|
||||
}
|
||||
|
||||
let ret_var = type_to_variable(subs, aliases, *ret_type);
|
||||
let ret_var = type_to_variable(subs, rank, pools, aliases, ret_type);
|
||||
let content = Content::Structure(FlatType::Func(arg_vars, ret_var));
|
||||
|
||||
subs.fresh(Descriptor::from(content))
|
||||
register(subs, rank, pools, content)
|
||||
}
|
||||
Record(fields, ext) => {
|
||||
let mut field_vars = ImMap::default();
|
||||
|
||||
for (field, field_type) in fields {
|
||||
field_vars.insert(field, type_to_variable(subs, aliases, field_type));
|
||||
field_vars.insert(
|
||||
field.clone(),
|
||||
type_to_variable(subs, rank, pools, aliases, field_type),
|
||||
);
|
||||
}
|
||||
|
||||
let ext_var = type_to_variable(subs, aliases, *ext);
|
||||
let ext_var = type_to_variable(subs, rank, pools, aliases, ext);
|
||||
let content = Content::Structure(FlatType::Record(field_vars, ext_var));
|
||||
|
||||
subs.fresh(Descriptor::from(content))
|
||||
register(subs, rank, pools, content)
|
||||
}
|
||||
Alias(home, name, args, alias_type) => {
|
||||
let mut arg_vars = Vec::with_capacity(args.len());
|
||||
let mut new_aliases = ImMap::default();
|
||||
|
||||
for (arg, arg_type) in args {
|
||||
let arg_var = type_to_variable(subs, aliases, arg_type.clone());
|
||||
let arg_var = type_to_variable(subs, rank, pools, aliases, arg_type);
|
||||
|
||||
arg_vars.push((arg.clone(), arg_var));
|
||||
new_aliases.insert(arg, arg_var);
|
||||
new_aliases.insert(arg.clone(), arg_var);
|
||||
}
|
||||
|
||||
let alias_var = type_to_variable(subs, &new_aliases, *alias_type);
|
||||
let content = Content::Alias(home, name, arg_vars, alias_var);
|
||||
let alias_var = type_to_variable(subs, rank, pools, &new_aliases, alias_type);
|
||||
let content = Content::Alias(home.clone(), name.clone(), arg_vars, alias_var);
|
||||
|
||||
subs.fresh(Descriptor::from(content))
|
||||
register(subs, rank, pools, content)
|
||||
}
|
||||
Erroneous(problem) => {
|
||||
let content = Content::Structure(FlatType::Erroneous(problem));
|
||||
let content = Content::Structure(FlatType::Erroneous(problem.clone()));
|
||||
|
||||
subs.fresh(Descriptor::from(content))
|
||||
register(subs, rank, pools, content)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -381,9 +469,8 @@ fn generalize(
|
|||
let young_vars = pools.get(young_rank);
|
||||
let rank_table = pool_to_rank_table(subs, young_mark, young_rank, young_vars);
|
||||
|
||||
// get the ranks right for each entry.
|
||||
// start at low ranks so that we only have to pass
|
||||
// over the information once.
|
||||
// Get the ranks right for each entry.
|
||||
// Start at low ranks so we only have to pass over the information once.
|
||||
for (index, table) in rank_table.iter().enumerate() {
|
||||
for &var in table.iter() {
|
||||
adjust_rank(subs, young_mark, visit_mark, Rank::from(index), var);
|
||||
|
@ -392,7 +479,7 @@ fn generalize(
|
|||
|
||||
let (last_pool, all_but_last_pool) = rank_table.split_last();
|
||||
|
||||
// For variables that have rank lowerer than youngRank, register them in
|
||||
// For variables that have rank lowerer than young_rank, register them in
|
||||
// the appropriate old pool if they are not redundant.
|
||||
for vars in all_but_last_pool {
|
||||
for &var in vars {
|
||||
|
@ -404,9 +491,8 @@ fn generalize(
|
|||
}
|
||||
}
|
||||
|
||||
// For variables with rank youngRank
|
||||
// If rank < youngRank: register in oldPool
|
||||
// otherwise generalize
|
||||
// For variables with rank young_rank, if rank < young_rank: register in old pool,
|
||||
// otherwise generalize
|
||||
for &var in last_pool {
|
||||
if !subs.redundant(var) {
|
||||
let mut desc = subs.get(var);
|
||||
|
@ -414,7 +500,7 @@ fn generalize(
|
|||
if desc.rank < young_rank {
|
||||
pools.get_mut(desc.rank).push(var);
|
||||
} else {
|
||||
desc.rank = Rank::none();
|
||||
desc.rank = Rank::NONE;
|
||||
|
||||
subs.set(var, desc);
|
||||
}
|
||||
|
@ -432,9 +518,18 @@ fn pool_to_rank_table(
|
|||
|
||||
// Sort the variables into buckets by rank.
|
||||
for &var in young_vars.iter() {
|
||||
let rank = subs.get(var).rank;
|
||||
let desc = subs.get(var);
|
||||
let rank = desc.rank;
|
||||
|
||||
subs.set_mark(var, young_mark);
|
||||
subs.set(
|
||||
var,
|
||||
Descriptor {
|
||||
rank,
|
||||
mark: young_mark,
|
||||
content: desc.content,
|
||||
copy: desc.copy,
|
||||
},
|
||||
);
|
||||
|
||||
pools.get_mut(rank).push(var);
|
||||
}
|
||||
|
@ -444,7 +539,6 @@ fn pool_to_rank_table(
|
|||
|
||||
/// Adjust variable ranks such that ranks never increase as you move deeper.
|
||||
/// This way the outermost rank is representative of the entire structure.
|
||||
///
|
||||
fn adjust_rank(
|
||||
subs: &mut Subs,
|
||||
young_mark: Mark,
|
||||
|
@ -473,7 +567,7 @@ fn adjust_rank(
|
|||
} else if mark == visit_mark {
|
||||
desc.rank
|
||||
} else {
|
||||
let min_rank = desc.rank.min(group_rank);
|
||||
let min_rank = group_rank.min(desc.rank);
|
||||
|
||||
// TODO from elm-compiler: how can min_rank ever be group_rank?
|
||||
desc.rank = min_rank;
|
||||
|
@ -501,7 +595,7 @@ fn adjust_rank_content(
|
|||
Structure(flat_type) => {
|
||||
match flat_type {
|
||||
Apply { args, .. } => {
|
||||
let mut rank = Rank::outermost();
|
||||
let mut rank = Rank::toplevel();
|
||||
|
||||
for var in args {
|
||||
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
|
||||
|
@ -522,7 +616,7 @@ fn adjust_rank_content(
|
|||
|
||||
EmptyRecord => {
|
||||
// from elm-compiler: THEORY: an empty record never needs to get generalized
|
||||
Rank::outermost()
|
||||
Rank::toplevel()
|
||||
}
|
||||
|
||||
Record(fields, ext_var) => {
|
||||
|
@ -539,9 +633,9 @@ fn adjust_rank_content(
|
|||
}
|
||||
|
||||
Alias(_, _, args, _) => {
|
||||
let mut rank = Rank::outermost();
|
||||
let mut rank = Rank::toplevel();
|
||||
|
||||
// from elm-compiler: THEORY: anything in the realVar would be outermostRank
|
||||
// from elm-compiler: THEORY: anything in the real_var would be Rank::toplevel()
|
||||
for (_, var) in args {
|
||||
rank = rank.max(adjust_rank(subs, young_mark, visit_mark, group_rank, var));
|
||||
}
|
||||
|
@ -551,6 +645,8 @@ fn adjust_rank_content(
|
|||
}
|
||||
}
|
||||
|
||||
/// Introduce some variables to Pools at the given rank.
|
||||
/// Also, set each of their ranks in Subs to be the given rank.
|
||||
fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable]) {
|
||||
let pool: &mut Vec<Variable> = pools.get_mut(rank);
|
||||
|
||||
|
@ -560,3 +656,16 @@ fn introduce(subs: &mut Subs, rank: Rank, pools: &mut Pools, vars: &[Variable])
|
|||
|
||||
pool.extend(vars);
|
||||
}
|
||||
|
||||
fn register(subs: &mut Subs, rank: Rank, pools: &mut Pools, content: Content) -> Variable {
|
||||
let var = subs.fresh(Descriptor {
|
||||
content,
|
||||
rank,
|
||||
mark: Mark::NONE,
|
||||
copy: None,
|
||||
});
|
||||
|
||||
pools.get_mut(rank).push(var);
|
||||
|
||||
var
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue