Polymorphic expression specialization works in gen tests

This commit is contained in:
Ayaz Hafiz 2022-05-03 17:46:30 -04:00
parent f21ce43e30
commit 261df36c50
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
7 changed files with 363 additions and 206 deletions

View file

@ -17,6 +17,9 @@ pub fn deep_copy_type_vars_into_expr<'a>(
var: Variable,
expr: &Expr,
) -> Option<(Variable, Expr)> {
// Always deal with the root, so that aliases propagate correctly.
let var = subs.get_root_key_without_compacting(var);
let substitutions = deep_copy_type_vars(arena, subs, var);
if substitutions.is_empty() {
@ -28,21 +31,23 @@ pub fn deep_copy_type_vars_into_expr<'a>(
.find_map(|&(original, new)| if original == var { Some(new) } else { None })
.expect("Variable marked as cloned, but it isn't");
return Some((new_var, help(expr, &substitutions)));
return Some((new_var, help(subs, expr, &substitutions)));
fn help(expr: &Expr, substitutions: &[(Variable, Variable)]) -> Expr {
fn help(subs: &Subs, expr: &Expr, substitutions: &[(Variable, Variable)]) -> Expr {
use Expr::*;
macro_rules! sub {
($var:expr) => {
($var:expr) => {{
// Always deal with the root, so that aliases propagate correctly.
let root = subs.get_root_key_without_compacting($var);
substitutions
.iter()
.find_map(|&(original, new)| if original == $var { Some(new) } else { None })
.find_map(|&(original, new)| if original == root { Some(new) } else { None })
.unwrap_or($var)
};
}};
}
let go_help = |e: &Expr| help(e, substitutions);
let go_help = |e: &Expr| help(subs, e, substitutions);
match expr {
Num(var, str, val, bound) => Num(sub!(*var), str.clone(), val.clone(), *bound),
@ -378,15 +383,13 @@ fn deep_copy_type_vars<'a>(
subs: &mut Subs,
var: Variable,
) -> Vec<'a, (Variable, Variable)> {
// Always deal with the root, so that unified variables are treated the same.
let var = subs.get_root_key_without_compacting(var);
let mut copied = Vec::with_capacity_in(16, arena);
let cloned_var = help(arena, subs, &mut copied, var);
debug_assert!(match cloned_var {
Some(_) => !copied.is_empty(),
None => copied.is_empty(),
});
// we have tracked all visited variables, and can now traverse them
// in one go (without looking at the UnificationTable) and clear the copy field
let mut result = Vec::with_capacity_in(copied.len(), arena);
@ -404,41 +407,48 @@ fn deep_copy_type_vars<'a>(
return result;
#[must_use]
fn help(
arena: &Bump,
subs: &mut Subs,
visited: &mut Vec<Variable>,
var: Variable,
) -> Option<Variable> {
fn help(arena: &Bump, subs: &mut Subs, visited: &mut Vec<Variable>, var: Variable) -> Variable {
use roc_types::subs::Content::*;
use roc_types::subs::FlatType::*;
// Always deal with the root, so that unified variables are treated the same.
let var = subs.get_root_key_without_compacting(var);
let desc = subs.get_ref_mut(var);
let content = desc.content;
let rank = desc.rank;
let mark = desc.mark;
// Unlike `deep_copy_var` in solve, here we are cloning *all* flex and rigid vars.
// So we only want to short-circuit if we've already done the cloning work for a particular
// var.
if let Some(copy) = desc.copy.into_variable() {
return Some(copy);
return copy;
}
let content = desc.content;
let copy_descriptor = Descriptor {
content: Error, // we'll update this below
rank: desc.rank,
mark: desc.mark,
copy: OptVariable::NONE,
};
let copy = subs.fresh(copy_descriptor);
subs.get_ref_mut(var).copy = copy.into();
visited.push(var);
macro_rules! descend_slice {
($slice:expr, $needs_clone:ident) => {
($slice:expr) => {
for var_index in $slice {
let var = subs[var_index];
$needs_clone = $needs_clone || help(arena, subs, visited, var).is_some();
let _ = help(arena, subs, visited, var);
}
};
}
macro_rules! descend_var {
($var:expr, $needs_clone:ident) => {{
let new_var = help(arena, subs, visited, $var).unwrap_or($var);
$needs_clone = $needs_clone || new_var != $var;
new_var
($var:expr) => {{
help(arena, subs, visited, $var)
}};
}
@ -455,65 +465,56 @@ fn deep_copy_type_vars<'a>(
}
macro_rules! perform_clone {
($needs_clone:ident, $do_clone:expr) => {
if $needs_clone {
// It may the case that while deep-copying nested variables of this type, we
// ended up copying the type itself (notably if it was self-referencing, in a
// recursive type). In that case, short-circuit with the known copy.
if let Some(copy) = subs.get_ref(var).copy.into_variable() {
return Some(copy);
}
// Perform the clone.
Some($do_clone)
} else {
None
}
};
($do_clone:expr) => {{
// It may the case that while deep-copying nested variables of this type, we
// ended up copying the type itself (notably if it was self-referencing, in a
// recursive type). In that case, short-circuit with the known copy.
// if let Some(copy) = subs.get_ref(var).copy.into_variable() {
// return copy;
// }
// Perform the clone.
$do_clone
}};
}
// Now we recursively copy the content of the variable.
// We have already marked the variable as copied, so we
// will not repeat this work or crawl this variable again.
let opt_new_content = match content {
let new_content = match content {
// The vars for which we want to do something interesting.
FlexVar(opt_name) => Some(FlexVar(opt_name)),
FlexAbleVar(opt_name, ability) => Some(FlexAbleVar(opt_name, ability)),
RigidVar(name) => Some(RigidVar(name)),
RigidAbleVar(name, ability) => Some(RigidAbleVar(name, ability)),
FlexVar(opt_name) => FlexVar(opt_name),
FlexAbleVar(opt_name, ability) => FlexAbleVar(opt_name, ability),
RigidVar(name) => RigidVar(name),
RigidAbleVar(name, ability) => RigidAbleVar(name, ability),
// Everything else is a mechanical descent.
Structure(flat_type) => match flat_type {
EmptyRecord | EmptyTagUnion | Erroneous(_) => None,
EmptyRecord | EmptyTagUnion | Erroneous(_) => Structure(flat_type.clone()),
Apply(symbol, arguments) => {
let mut needs_clone = false;
descend_slice!(arguments, needs_clone);
descend_slice!(arguments);
perform_clone!(needs_clone, {
perform_clone!({
let new_arguments = clone_var_slice!(arguments);
Structure(Apply(symbol, new_arguments))
})
}
Func(arguments, closure_var, ret_var) => {
let mut needs_clone = false;
descend_slice!(arguments);
descend_slice!(arguments, needs_clone);
let new_closure_var = descend_var!(closure_var);
let new_ret_var = descend_var!(ret_var);
let new_closure_var = descend_var!(closure_var, needs_clone);
let new_ret_var = descend_var!(ret_var, needs_clone);
perform_clone!(needs_clone, {
perform_clone!({
let new_arguments = clone_var_slice!(arguments);
Structure(Func(new_arguments, new_closure_var, new_ret_var))
})
}
Record(fields, ext_var) => {
let mut needs_clone = false;
let new_ext_var = descend_var!(ext_var);
let new_ext_var = descend_var!(ext_var, needs_clone);
descend_slice!(fields.variables());
descend_slice!(fields.variables(), needs_clone);
perform_clone!(needs_clone, {
perform_clone!({
let new_variables = clone_var_slice!(fields.variables());
let new_fields = {
RecordFields {
@ -528,16 +529,14 @@ fn deep_copy_type_vars<'a>(
})
}
TagUnion(tags, ext_var) => {
let mut needs_clone = false;
let new_ext_var = descend_var!(ext_var, needs_clone);
let new_ext_var = descend_var!(ext_var);
for variables_slice_index in tags.variables() {
let variables_slice = subs[variables_slice_index];
descend_slice!(variables_slice, needs_clone);
descend_slice!(variables_slice);
}
perform_clone!(needs_clone, {
perform_clone!({
let new_variable_slices =
SubsSlice::reserve_variable_slices(subs, tags.len());
let it = (new_variable_slices.indices()).zip(tags.variables());
@ -554,17 +553,15 @@ fn deep_copy_type_vars<'a>(
})
}
RecursiveTagUnion(rec_var, tags, ext_var) => {
let mut needs_clone = false;
let new_ext_var = descend_var!(ext_var, needs_clone);
let new_rec_var = descend_var!(rec_var, needs_clone);
let new_ext_var = descend_var!(ext_var);
let new_rec_var = descend_var!(rec_var);
for variables_slice_index in tags.variables() {
let variables_slice = subs[variables_slice_index];
descend_slice!(variables_slice, needs_clone);
descend_slice!(variables_slice);
}
perform_clone!(needs_clone, {
perform_clone!({
let new_variable_slices =
SubsSlice::reserve_variable_slices(subs, tags.len());
let it = (new_variable_slices.indices()).zip(tags.variables());
@ -581,11 +578,8 @@ fn deep_copy_type_vars<'a>(
})
}
FunctionOrTagUnion(tag_name, symbol, ext_var) => {
let mut needs_clone = false;
let new_ext_var = descend_var!(ext_var, needs_clone);
perform_clone!(needs_clone, {
Structure(FunctionOrTagUnion(tag_name, symbol, new_ext_var))
})
let new_ext_var = descend_var!(ext_var);
perform_clone!(Structure(FunctionOrTagUnion(tag_name, symbol, new_ext_var)))
}
},
@ -593,11 +587,9 @@ fn deep_copy_type_vars<'a>(
opt_name,
structure,
} => {
let mut needs_clone = false;
let new_structure = descend_var!(structure);
let new_structure = descend_var!(structure, needs_clone);
perform_clone!(needs_clone, {
perform_clone!({
RecursionVar {
opt_name,
structure: new_structure,
@ -606,12 +598,10 @@ fn deep_copy_type_vars<'a>(
}
Alias(symbol, arguments, real_type_var, kind) => {
let mut needs_clone = false;
let new_real_type_var = descend_var!(real_type_var);
descend_slice!(arguments.all_variables());
let new_real_type_var = descend_var!(real_type_var, needs_clone);
descend_slice!(arguments.all_variables(), needs_clone);
perform_clone!(needs_clone, {
perform_clone!({
let new_variables = clone_var_slice!(arguments.all_variables());
let new_arguments = AliasVariables {
variables_start: new_variables.start,
@ -623,41 +613,21 @@ fn deep_copy_type_vars<'a>(
}
RangedNumber(typ, range_vars) => {
let mut needs_clone = false;
let new_typ = descend_var!(typ);
descend_slice!(range_vars);
let new_typ = descend_var!(typ, needs_clone);
descend_slice!(range_vars, needs_clone);
perform_clone!(needs_clone, {
perform_clone!({
let new_range_vars = clone_var_slice!(range_vars);
RangedNumber(new_typ, new_range_vars)
})
}
Error => None,
Error => Error,
};
if let Some(new_content) = opt_new_content {
visited.push(var);
subs.set_content(copy, new_content);
let copy_descriptor = Descriptor {
content: new_content,
rank,
mark,
copy: OptVariable::NONE,
};
let copy = subs.fresh(copy_descriptor);
// Set the copy on the original var
subs.get_ref_mut(var).copy = copy.into();
// We had to create a fresh var for this type, so anything that depends on it should be
// freshened too, and use this fresh var.
return Some(copy);
}
// Doesn't need to be freshened; use the old var.
None
copy
}
}
@ -777,50 +747,6 @@ mod test {
}
}
#[test]
fn types_without_type_vars_should_not_be_copied() {
let mut subs = Subs::new();
let arena = Bump::new();
let cases = &[
RecursionVar {
structure: new_var(&mut subs, Structure(EmptyTagUnion)),
opt_name: None,
},
Structure(Record(
RecordFields::insert_into_subs(
&mut subs,
[("a".into(), RecordField::Required(Variable::BOOL))],
),
Variable::EMPTY_RECORD,
)),
Structure(TagUnion(
UnionTags::insert_into_subs(
&mut subs,
[(TagName::Tag("A".into()), [Variable::BOOL])],
),
Variable::EMPTY_TAG_UNION,
)),
Structure(RecursiveTagUnion(
Variable::EMPTY_TAG_UNION,
UnionTags::insert_into_subs(
&mut subs,
[(TagName::Tag("A".into()), [Variable::BOOL])],
),
Variable::EMPTY_TAG_UNION,
)),
Error,
];
for &content in cases {
let var = new_var(&mut subs, content);
let copies = deep_copy_type_vars(&arena, &mut subs, var);
assert!(copies.is_empty());
}
}
#[test]
fn copy_type_with_copied_reference() {
let mut subs = Subs::new();