mirror of
https://github.com/roc-lang/roc.git
synced 2025-10-02 00:01:16 +00:00
Polymorphic expression specialization works in gen tests
This commit is contained in:
parent
f21ce43e30
commit
261df36c50
7 changed files with 363 additions and 206 deletions
|
@ -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();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue