Check self- and mutually-recursive aliases in the same pass

This commit is contained in:
ayazhafiz 2022-02-10 08:15:48 -05:00
parent c99f8b23e7
commit 13552b11a6
2 changed files with 113 additions and 100 deletions

View file

@ -22,7 +22,7 @@ use roc_types::subs::{VarStore, Variable};
use roc_types::types::{Alias, Type}; use roc_types::types::{Alias, Type};
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use ven_graph::{strongly_connected_components, topological_sort, topological_sort_into_groups}; use ven_graph::{strongly_connected_components, topological_sort};
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Def { pub struct Def {
@ -265,8 +265,7 @@ pub fn canonicalize_defs<'a>(
let (name, vars, ann) = alias_defs.remove(&alias_name).unwrap(); let (name, vars, ann) = alias_defs.remove(&alias_name).unwrap();
let symbol = name.value; let symbol = name.value;
let mut can_ann = let can_ann = canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
canonicalize_annotation(env, &mut scope, &ann.value, ann.region, var_store);
// Record all the annotation's references in output.references.lookups // Record all the annotation's references in output.references.lookups
for symbol in can_ann.references { for symbol in can_ann.references {
@ -303,35 +302,6 @@ pub fn canonicalize_defs<'a>(
continue; continue;
} }
let mut is_nested_datatype = false;
if can_ann.typ.contains_symbol(symbol) {
let alias_args = can_vars
.iter()
.map(|l| (l.value.0.clone(), Type::Variable(l.value.1)))
.collect::<Vec<_>>();
let alias_region =
Region::across_all([name.region].iter().chain(vars.iter().map(|l| &l.region)));
let made_recursive = make_tag_union_recursive(
env,
Loc::at(alias_region, (symbol, &alias_args)),
name.region,
vec![],
&mut can_ann.typ,
var_store,
// Don't report any errors yet. We'll take care of self and mutual
// recursion errors after the sorted introductions are complete.
&mut false,
);
is_nested_datatype = made_recursive.is_err();
}
if is_nested_datatype {
// Bail out
continue;
}
scope.add_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone()); scope.add_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone());
let alias = scope.lookup_alias(symbol).expect("alias is added to scope"); let alias = scope.lookup_alias(symbol).expect("alias is added to scope");
aliases.insert(symbol, alias.clone()); aliases.insert(symbol, alias.clone());
@ -339,7 +309,7 @@ pub fn canonicalize_defs<'a>(
// Now that we know the alias dependency graph, we can try to insert recursion variables // Now that we know the alias dependency graph, we can try to insert recursion variables
// where aliases are recursive tag unions, or detect illegal recursions. // where aliases are recursive tag unions, or detect illegal recursions.
correct_mutual_recursive_type_alias(env, &mut aliases, var_store); correct_mutual_recursive_type_alias(env, &mut scope, &mut aliases, var_store);
// Now that we have the scope completely assembled, and shadowing resolved, // Now that we have the scope completely assembled, and shadowing resolved,
// we're ready to canonicalize any body exprs. // we're ready to canonicalize any body exprs.
@ -1564,6 +1534,7 @@ fn pending_typed_body<'a>(
/// Make aliases recursive /// Make aliases recursive
fn correct_mutual_recursive_type_alias<'a>( fn correct_mutual_recursive_type_alias<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
scope: &mut Scope,
aliases: &mut SendMap<Symbol, Alias>, aliases: &mut SendMap<Symbol, Alias>,
var_store: &mut VarStore, var_store: &mut VarStore,
) { ) {
@ -1586,47 +1557,68 @@ fn correct_mutual_recursive_type_alias<'a>(
} }
}; };
let all_successors_without_self = |symbol: &Symbol| -> ImSet<Symbol> {
match aliases.get(symbol) {
Some(alias) => {
let mut loc_succ = alias.typ.symbols();
// remove anything that is not defined in the current block
loc_succ.retain(|key| symbols_introduced.contains(key));
loc_succ.remove(symbol);
loc_succ
}
None => ImSet::default(),
}
};
let originals = aliases.clone();
// TODO investigate should this be in a loop? // TODO investigate should this be in a loop?
let defined_symbols: Vec<Symbol> = aliases.keys().copied().collect(); let defined_symbols: Vec<Symbol> = aliases.keys().copied().collect();
// split into self-recursive and mutually recursive let cycles = &strongly_connected_components(&defined_symbols, all_successors_with_self);
match topological_sort_into_groups(&defined_symbols, all_successors_with_self) {
Ok(_) => { for cycle in cycles {
// no mutual recursion in any alias debug_assert!(!cycle.is_empty());
if cycle.len() == 1 {
let symbol = cycle[0];
let alias = aliases.get_mut(&symbol).unwrap();
if alias.typ.contains_symbol(symbol) {
let mut can_still_report_error = true;
let mut opt_rec_var = None;
let _made_recursive = make_tag_union_of_alias_recursive(
env,
symbol,
alias,
vec![],
var_store,
&mut can_still_report_error,
&mut opt_rec_var,
);
scope.add_alias(
symbol,
alias.region,
alias.type_variables.clone(),
alias.typ.clone(),
);
} }
Err((_, mutually_recursive_symbols)) => { } else {
for cycle in strongly_connected_components( // This is a mutually recursive cycle
&mutually_recursive_symbols,
all_successors_without_self,
) {
// make sure we report only one error for the cycle, not an error for every // make sure we report only one error for the cycle, not an error for every
// alias in the cycle. // alias in the cycle.
let mut can_still_report_error = true; let mut can_still_report_error = true;
let mut opt_rec_var = None;
for symbol in cycle {
let alias = aliases.get_mut(&symbol).unwrap();
let _made_recursive = make_tag_union_of_alias_recursive(
env,
*symbol,
alias,
vec![],
var_store,
&mut can_still_report_error,
&mut opt_rec_var,
);
}
// TODO use itertools to be more efficient here // TODO use itertools to be more efficient here
for rec in &cycle { for rec in cycle {
let mut to_instantiate = ImMap::default(); let mut to_instantiate = ImMap::default();
let mut others = Vec::with_capacity(cycle.len() - 1); let mut others = Vec::with_capacity(cycle.len() - 1);
for other in &cycle { for other in cycle {
if rec != other { if rec != other {
others.push(*other); others.push(*other);
if let Some(alias) = originals.get(other) { if let Some(alias) = aliases.get(other) {
to_instantiate.insert(*other, alias.clone()); to_instantiate.insert(*other, alias.clone());
} }
} }
@ -1639,27 +1631,37 @@ fn correct_mutual_recursive_type_alias<'a>(
var_store, var_store,
&mut ImSet::default(), &mut ImSet::default(),
); );
}
}
}
}
}
let alias_args = &alias fn make_tag_union_of_alias_recursive<'a>(
env: &mut Env<'a>,
alias_name: Symbol,
alias: &mut Alias,
others: Vec<Symbol>,
var_store: &mut VarStore,
can_report_error: &mut bool,
opt_rec_var: &mut Option<Variable>,
) -> Result<(), ()> {
let alias_args = alias
.type_variables .type_variables
.iter() .iter()
.map(|l| (l.value.0.clone(), Type::Variable(l.value.1))) .map(|l| (l.value.0.clone(), Type::Variable(l.value.1)))
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let _made_recursive = make_tag_union_recursive( make_tag_union_recursive_help(
env, env,
Loc::at(alias.header_region(), (*rec, alias_args)), Loc::at(alias.header_region(), (alias_name, &alias_args)),
alias.region, alias.region,
others, others,
&mut alias.typ, &mut alias.typ,
var_store, var_store,
&mut can_still_report_error, can_report_error,
); opt_rec_var,
} )
}
}
}
}
} }
/// Attempt to make a tag union recursive at the position of `recursive_alias`; for example, /// Attempt to make a tag union recursive at the position of `recursive_alias`; for example,
@ -1683,7 +1685,7 @@ fn correct_mutual_recursive_type_alias<'a>(
/// ``` /// ```
/// ///
/// When `Err` is returned, a problem will be added to `env`. /// When `Err` is returned, a problem will be added to `env`.
fn make_tag_union_recursive<'a>( fn make_tag_union_recursive_help<'a>(
env: &mut Env<'a>, env: &mut Env<'a>,
recursive_alias: Loc<(Symbol, &[(Lowercase, Type)])>, recursive_alias: Loc<(Symbol, &[(Lowercase, Type)])>,
region: Region, region: Region,
@ -1691,6 +1693,7 @@ fn make_tag_union_recursive<'a>(
typ: &mut Type, typ: &mut Type,
var_store: &mut VarStore, var_store: &mut VarStore,
can_report_error: &mut bool, can_report_error: &mut bool,
opt_rec_var: &mut Option<Variable>,
) -> Result<(), ()> { ) -> Result<(), ()> {
let Loc { let Loc {
value: (symbol, args), value: (symbol, args),
@ -1699,7 +1702,10 @@ fn make_tag_union_recursive<'a>(
let vars = args.iter().map(|(_, t)| t.clone()).collect::<Vec<_>>(); let vars = args.iter().map(|(_, t)| t.clone()).collect::<Vec<_>>();
match typ { match typ {
Type::TagUnion(tags, ext) => { Type::TagUnion(tags, ext) => {
let rec_var = var_store.fresh(); if opt_rec_var.is_none() {
*opt_rec_var = Some(var_store.fresh());
}
let rec_var = opt_rec_var.unwrap();
let mut pending_typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone()); let mut pending_typ = Type::RecursiveTagUnion(rec_var, tags.to_vec(), ext.clone());
let substitution_result = let substitution_result =
pending_typ.substitute_alias(symbol, &vars, &Type::Variable(rec_var)); pending_typ.substitute_alias(symbol, &vars, &Type::Variable(rec_var));
@ -1724,7 +1730,7 @@ fn make_tag_union_recursive<'a>(
actual, actual,
type_arguments, type_arguments,
.. ..
} => make_tag_union_recursive( } => make_tag_union_recursive_help(
env, env,
Loc::at_zero((symbol, type_arguments)), Loc::at_zero((symbol, type_arguments)),
region, region,
@ -1732,6 +1738,7 @@ fn make_tag_union_recursive<'a>(
actual, actual,
var_store, var_store,
can_report_error, can_report_error,
opt_rec_var,
), ),
_ => { _ => {
let problem = roc_types::types::Problem::CyclicAlias(symbol, region, others.clone()); let problem = roc_types::types::Problem::CyclicAlias(symbol, region, others.clone());

View file

@ -595,9 +595,15 @@ impl Type {
ext.substitute_alias(rep_symbol, rep_args, actual) ext.substitute_alias(rep_symbol, rep_args, actual)
} }
Alias { Alias {
type_arguments,
actual: alias_actual, actual: alias_actual,
.. ..
} => alias_actual.substitute_alias(rep_symbol, rep_args, actual), } => {
for (_, ta) in type_arguments {
ta.substitute_alias(rep_symbol, rep_args, actual)?;
}
alias_actual.substitute_alias(rep_symbol, rep_args, actual)
}
HostExposedAlias { HostExposedAlias {
actual: actual_type, actual: actual_type,
.. ..