move over constraint

This commit is contained in:
Folkert 2022-03-02 21:19:25 +01:00
parent 8d2e0a738c
commit 0eb98a4c59
No known key found for this signature in database
GPG key ID: 1F17F6FFD112B97C
11 changed files with 429 additions and 1417 deletions

View file

@ -1,5 +1,5 @@
use bumpalo::Bump;
use roc_can::constraint::Constraint;
use roc_can::constraint::{Constraint, Constraints};
use roc_can::expected::{Expected, PExpected};
use roc_collections::all::MutMap;
use roc_module::ident::TagName;
@ -168,56 +168,12 @@ struct State {
mark: Mark,
}
pub fn run(
env: &Env,
problems: &mut Vec<TypeError>,
mut subs: Subs,
constraint: &Constraint,
) -> (Solved<Subs>, Env) {
let env = run_in_place(env, problems, &mut subs, constraint);
(Solved(subs), env)
}
/// Modify an existing subs in-place instead
pub fn run_in_place(
env: &Env,
problems: &mut Vec<TypeError>,
subs: &mut Subs,
constraint: &Constraint,
) -> Env {
let mut pools = Pools::default();
let state = State {
env: env.clone(),
mark: Mark::NONE.next(),
};
let rank = Rank::toplevel();
let arena = Bump::new();
let state = solve(
&arena,
env,
state,
rank,
&mut pools,
problems,
&mut MutMap::default(),
subs,
constraint,
);
state.env
}
use roc_can::constraint_soa::{Constraint as ConstraintSoa, Constraints};
pub fn run_soa(
constraints: &Constraints,
env: &Env,
problems: &mut Vec<TypeError>,
mut subs: Subs,
constraint: &ConstraintSoa,
constraint: &Constraint,
) -> (Solved<Subs>, Env) {
let env = run_in_place_soa(constraints, env, problems, &mut subs, constraint);
@ -230,7 +186,7 @@ pub fn run_in_place_soa(
env: &Env,
problems: &mut Vec<TypeError>,
subs: &mut Subs,
constraint: &ConstraintSoa,
constraint: &Constraint,
) -> Env {
let mut pools = Pools::default();
let state = State {
@ -261,553 +217,20 @@ enum After {
CheckForInfiniteTypes(LocalDefVarsVec<(Symbol, Loc<Variable>)>),
}
enum Work<'a> {
Constraint {
env: &'a Env,
rank: Rank,
constraint: &'a Constraint,
after: Option<After>,
},
/// Something to be done after a constraint and all its dependencies are fully solved.
After(After),
}
enum WorkSoa<'a> {
Constraint {
env: &'a Env,
rank: Rank,
constraint: &'a roc_can::constraint_soa::Constraint,
constraint: &'a Constraint,
},
/// Something to be done after a constraint and all its dependencies are fully solved.
After(After),
}
#[allow(clippy::too_many_arguments)]
fn solve(
arena: &Bump,
env: &Env,
mut state: State,
rank: Rank,
pools: &mut Pools,
problems: &mut Vec<TypeError>,
cached_aliases: &mut MutMap<Symbol, Variable>,
subs: &mut Subs,
constraint: &roc_can::constraint::Constraint,
) -> State {
use roc_can::constraint::PresenceConstraint;
use Constraint::*;
let mut stack = vec![Work::Constraint {
env,
rank,
constraint,
after: None,
}];
while let Some(work_item) = stack.pop() {
let (env, rank, constraint) = match work_item {
Work::After(After::CheckForInfiniteTypes(def_vars)) => {
for (symbol, loc_var) in def_vars.iter() {
check_for_infinite_type(subs, problems, *symbol, *loc_var);
}
// No constraint to be solved
continue;
}
Work::Constraint {
env,
rank,
constraint,
after,
} => {
// Push the `after` on first so that we look at it immediately after finishing all
// the children of this constraint.
if let Some(after) = after {
stack.push(Work::After(after));
}
(env, rank, constraint)
}
};
state = match constraint {
True => state,
SaveTheEnvironment => {
// NOTE deviation: elm only copies the env into the state on SaveTheEnvironment
let mut copy = state;
copy.env = env.clone();
copy
}
Eq(typ, expectation, category, region) => {
let actual = type_to_var(subs, rank, pools, cached_aliases, typ);
let expected = type_to_var(
subs,
rank,
pools,
cached_aliases,
expectation.get_type_ref(),
);
match unify(subs, actual, expected, Mode::EQ) {
Success(vars) => {
introduce(subs, rank, pools, &vars);
state
}
Failure(vars, actual_type, expected_type) => {
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadExpr(
*region,
category.clone(),
actual_type,
expectation.clone().replace(expected_type),
);
problems.push(problem);
state
}
BadType(vars, problem) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::BadType(problem));
state
}
}
}
Store(source, target, _filename, _linenr) => {
// a special version of Eq that is used to store types in the AST.
// IT DOES NOT REPORT ERRORS!
let actual = type_to_var(subs, rank, pools, cached_aliases, source);
let target = *target;
match unify(subs, actual, target, Mode::EQ) {
Success(vars) => {
introduce(subs, rank, pools, &vars);
state
}
Failure(vars, _actual_type, _expected_type) => {
introduce(subs, rank, pools, &vars);
// ERROR NOT REPORTED
state
}
BadType(vars, _) => {
introduce(subs, rank, pools, &vars);
// ERROR NOT REPORTED
state
}
}
}
Lookup(symbol, expectation, region) => {
match env.get_var_by_symbol(symbol) {
Some(var) => {
// 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.
//
// If this Lookup is targeting a value in another module,
// then we copy from that module's Subs into our own. If the value
// is being looked up in this module, then we use our Subs as both
// the source and destination.
let actual = deep_copy_var(subs, rank, pools, var);
let expected = type_to_var(
subs,
rank,
pools,
cached_aliases,
expectation.get_type_ref(),
);
match unify(subs, actual, expected, Mode::EQ) {
Success(vars) => {
introduce(subs, rank, pools, &vars);
state
}
Failure(vars, actual_type, expected_type) => {
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadExpr(
*region,
Category::Lookup(*symbol),
actual_type,
expectation.clone().replace(expected_type),
);
problems.push(problem);
state
}
BadType(vars, problem) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::BadType(problem));
state
}
}
}
None => {
problems.push(TypeError::UnexposedLookup(*symbol));
state
}
}
}
And(sub_constraints) => {
for sub_constraint in sub_constraints.iter().rev() {
stack.push(Work::Constraint {
env,
rank,
constraint: sub_constraint,
after: None,
})
}
state
}
Pattern(region, category, typ, expectation)
| Present(typ, PresenceConstraint::Pattern(region, category, expectation)) => {
let actual = type_to_var(subs, rank, pools, cached_aliases, typ);
let expected = type_to_var(
subs,
rank,
pools,
cached_aliases,
expectation.get_type_ref(),
);
let mode = match constraint {
Present(_, _) => Mode::PRESENT,
_ => Mode::EQ,
};
match unify(subs, actual, expected, mode) {
Success(vars) => {
introduce(subs, rank, pools, &vars);
state
}
Failure(vars, actual_type, expected_type) => {
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadPattern(
*region,
category.clone(),
actual_type,
expectation.clone().replace(expected_type),
);
problems.push(problem);
state
}
BadType(vars, problem) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::BadType(problem));
state
}
}
}
Let(let_con) => {
match &let_con.ret_constraint {
True if let_con.rigid_vars.is_empty() => {
introduce(subs, rank, pools, &let_con.flex_vars);
// If the return expression is guaranteed to solve,
// solve the assignments themselves and move on.
stack.push(Work::Constraint {
env,
rank,
constraint: &let_con.defs_constraint,
after: None,
});
state
}
ret_con if let_con.rigid_vars.is_empty() && let_con.flex_vars.is_empty() => {
// TODO: make into `WorkItem` with `After`
let state = solve(
arena,
env,
state,
rank,
pools,
problems,
cached_aliases,
subs,
&let_con.defs_constraint,
);
// Add a variable for each def to new_vars_by_env.
let mut local_def_vars =
LocalDefVarsVec::with_length(let_con.def_types.len());
for (symbol, loc_type) in let_con.def_types.iter() {
let var =
type_to_var(subs, rank, pools, cached_aliases, &loc_type.value);
local_def_vars.push((
*symbol,
Loc {
value: var,
region: loc_type.region,
},
));
}
let mut new_env = env.clone();
for (symbol, loc_var) in local_def_vars.iter() {
new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value);
}
stack.push(Work::Constraint {
env: arena.alloc(new_env),
rank,
constraint: ret_con,
after: Some(After::CheckForInfiniteTypes(local_def_vars)),
});
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);
}
// determine the next pool
if next_rank.into_usize() < pools.len() {
// Nothing to do, we already accounted for the next rank, no need to
// adjust the pools
} else {
// we should be off by one at this point
debug_assert_eq!(next_rank.into_usize(), 1 + pools.len());
pools.extend_to(next_rank.into_usize());
}
let pool: &mut Vec<Variable> = 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());
// run solver in next pool
// Add a variable for each def to local_def_vars.
let mut local_def_vars =
LocalDefVarsVec::with_length(let_con.def_types.len());
for (symbol, loc_type) in let_con.def_types.iter() {
let def_type = &loc_type.value;
let var = type_to_var(subs, next_rank, pools, cached_aliases, def_type);
local_def_vars.push((
*symbol,
Loc {
value: var,
region: loc_type.region,
},
));
}
// Solve the assignments' constraints first.
// TODO: make into `WorkItem` with `After`
let State {
env: saved_env,
mark,
} = solve(
arena,
env,
state,
next_rank,
pools,
problems,
cached_aliases,
subs,
&let_con.defs_constraint,
);
let young_mark = mark;
let visit_mark = young_mark.next();
let final_mark = visit_mark.next();
debug_assert_eq!(
{
let offenders = pools
.get(next_rank)
.iter()
.filter(|var| {
let current_rank =
subs.get_rank(roc_types::subs::Variable::clone(var));
current_rank.into_usize() > next_rank.into_usize()
})
.collect::<Vec<_>>();
let result = offenders.len();
if result > 0 {
dbg!(&subs, &offenders, &let_con.def_types);
}
result
},
0
);
// pop pool
generalize(subs, young_mark, visit_mark, next_rank, pools);
pools.get_mut(next_rank).clear();
// check that things went well
debug_assert!({
// NOTE the `subs.redundant` check is added for the uniqueness
// inference, and does not come from elm. It's unclear whether this is
// a bug with uniqueness inference (something is redundant that
// shouldn't be) or that it just never came up in elm.
let failing: Vec<_> = rigid_vars
.iter()
.filter(|&var| {
!subs.redundant(*var) && subs.get_rank(*var) != Rank::NONE
})
.collect();
if !failing.is_empty() {
println!("Rigids {:?}", &rigid_vars);
println!("Failing {:?}", failing);
}
failing.is_empty()
});
let mut new_env = env.clone();
for (symbol, loc_var) in local_def_vars.iter() {
new_env.insert_symbol_var_if_vacant(*symbol, loc_var.value);
}
// Note that this vars_by_symbol is the one returned by the
// previous call to solve()
let state_for_ret_con = State {
env: saved_env,
mark: final_mark,
};
// Now solve the body, using the new vars_by_symbol which includes
// the assignments' name-to-variable mappings.
stack.push(Work::Constraint {
env: arena.alloc(new_env),
rank,
constraint: ret_con,
after: Some(After::CheckForInfiniteTypes(local_def_vars)),
});
state_for_ret_con
}
}
}
Present(typ, PresenceConstraint::IsOpen) => {
let actual = type_to_var(subs, rank, pools, cached_aliases, typ);
let mut new_desc = subs.get(actual);
match new_desc.content {
Content::Structure(FlatType::TagUnion(tags, _)) => {
let new_ext = subs.fresh_unnamed_flex_var();
let new_union = Content::Structure(FlatType::TagUnion(tags, new_ext));
new_desc.content = new_union;
subs.set(actual, new_desc);
state
}
_ => {
// Today, an "open" constraint doesn't affect any types
// other than tag unions. Recursive tag unions are constructed
// at a later time (during occurs checks after tag unions are
// resolved), so that's not handled here either.
// NB: Handle record types here if we add presence constraints
// to their type inference as well.
state
}
}
}
Present(
typ,
PresenceConstraint::IncludesTag(tag_name, tys, region, pattern_category),
) => {
let actual = type_to_var(subs, rank, pools, cached_aliases, typ);
let tag_ty = Type::TagUnion(
vec![(tag_name.clone(), tys.clone())],
Box::new(Type::EmptyTagUnion),
);
let includes = type_to_var(subs, rank, pools, cached_aliases, &tag_ty);
match unify(subs, actual, includes, Mode::PRESENT) {
Success(vars) => {
introduce(subs, rank, pools, &vars);
state
}
Failure(vars, actual_type, expected_to_include_type) => {
introduce(subs, rank, pools, &vars);
let problem = TypeError::BadPattern(
*region,
pattern_category.clone(),
expected_to_include_type,
PExpected::NoExpectation(actual_type),
);
problems.push(problem);
state
}
BadType(vars, problem) => {
introduce(subs, rank, pools, &vars);
problems.push(TypeError::BadType(problem));
state
}
}
}
};
}
state
}
#[allow(clippy::too_many_arguments)]
fn solve_soa(
arena: &Bump,
constraints: &roc_can::constraint_soa::Constraints,
constraints: &Constraints,
env: &Env,
mut state: State,
rank: Rank,
@ -815,9 +238,9 @@ fn solve_soa(
problems: &mut Vec<TypeError>,
cached_aliases: &mut MutMap<Symbol, Variable>,
subs: &mut Subs,
constraint: &roc_can::constraint_soa::Constraint,
constraint: &Constraint,
) -> State {
use roc_can::constraint_soa::Constraint::*;
use Constraint::*;
let initial = WorkSoa::Constraint {
env,
@ -1300,7 +723,7 @@ fn solve_soa(
IncludesTag(index) => {
let includes_tag = &constraints.includes_tags[index.index()];
let roc_can::constraint_soa::IncludesTag {
let roc_can::constraint::IncludesTag {
type_index,
tag_name,
types,