Report self-recursive aliases at their declaration site, not in usages

Closes #2380
This commit is contained in:
ayazhafiz 2022-01-22 14:26:32 -05:00
parent bec222b0c3
commit 48a3e871e8
4 changed files with 92 additions and 65 deletions

View file

@ -311,15 +311,19 @@ pub fn canonicalize_defs<'a>(
vec![], vec![],
&mut can_ann.typ, &mut can_ann.typ,
var_store, 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, &mut false,
); );
} }
scope.add_alias(symbol, ann.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());
} }
// 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.
correct_mutual_recursive_type_alias(env, &mut aliases, var_store); correct_mutual_recursive_type_alias(env, &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,
@ -962,66 +966,7 @@ fn canonicalize_pending_def<'a>(
} }
} }
Alias { Alias { .. } => unreachable!("Aliases are handled in a separate pass"),
name, ann, vars, ..
} => {
let symbol = name.value;
let can_ann = canonicalize_annotation(env, scope, &ann.value, ann.region, var_store);
// Record all the annotation's references in output.references.lookups
for symbol in can_ann.references {
output.references.lookups.insert(symbol);
output.references.referenced_aliases.insert(symbol);
}
let mut can_vars: Vec<Loc<(Lowercase, Variable)>> = Vec::with_capacity(vars.len());
for loc_lowercase in vars {
if let Some(var) = can_ann
.introduced_variables
.var_by_name(&loc_lowercase.value)
{
// This is a valid lowercase rigid var for the alias.
can_vars.push(Loc {
value: (loc_lowercase.value.clone(), *var),
region: loc_lowercase.region,
});
} else {
env.problems.push(Problem::PhantomTypeArgument {
alias: symbol,
variable_region: loc_lowercase.region,
variable_name: loc_lowercase.value.clone(),
});
}
}
scope.add_alias(symbol, name.region, can_vars.clone(), can_ann.typ.clone());
if can_ann.typ.contains_symbol(symbol) {
// the alias is recursive. If it's a tag union, we attempt to fix this
if let Type::TagUnion(tags, ext) = can_ann.typ {
// re-canonicalize the alias with the alias already in scope
let rec_var = var_store.fresh();
let mut rec_type_union = Type::RecursiveTagUnion(rec_var, tags, ext);
rec_type_union.substitute_alias(symbol, &Type::Variable(rec_var));
scope.add_alias(symbol, name.region, can_vars, rec_type_union);
} else {
env.problems
.push(Problem::CyclicAlias(symbol, name.region, vec![]));
return output;
}
}
let alias = scope.lookup_alias(symbol).expect("alias was not added");
aliases.insert(symbol, alias.clone());
output
.introduced_variables
.union(&can_ann.introduced_variables);
}
InvalidAlias => { InvalidAlias => {
// invalid aliases (shadowed, incorrect patterns) get ignored // invalid aliases (shadowed, incorrect patterns) get ignored
} }

View file

@ -886,6 +886,9 @@ fn symbols_help(tipe: &Type, accum: &mut ImSet<Symbol>) {
accum.insert(*symbol); accum.insert(*symbol);
args.iter().for_each(|arg| symbols_help(arg, accum)); args.iter().for_each(|arg| symbols_help(arg, accum));
} }
Erroneous(Problem::CyclicAlias(alias, _, _)) => {
accum.insert(*alias);
}
EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {} EmptyRec | EmptyTagUnion | ClosureTag { .. } | Erroneous(_) | Variable(_) => {}
} }
} }

View file

@ -100,10 +100,10 @@ pub fn type_problem<'b>(
report(title, doc, filename) report(title, doc, filename)
} }
CyclicAlias(symbol, region, others) => { CyclicAlias(..) => {
let (doc, title) = cyclic_alias(alloc, lines, symbol, region, others); // We'll also report cyclic aliases as a canonicalization problem, no need to
// re-report them.
report(title, doc, filename) None
} }
SolvedTypeError => None, // Don't re-report cascading errors - see https://github.com/rtfeldman/roc/pull/1711 SolvedTypeError => None, // Don't re-report cascading errors - see https://github.com/rtfeldman/roc/pull/1711

View file

@ -7074,4 +7074,83 @@ I need all branches in an `if` to have the same type!
), ),
) )
} }
#[test]
fn issue_2380_annotations_only() {
report_problem_as(
indoc!(
r#"
F : F
a : F
a
"#
),
indoc!(
r#"
CYCLIC ALIAS
The `F` alias is self-recursive in an invalid way:
1 F : F
^
Recursion in aliases is only allowed if recursion happens behind a
tag.
"#
),
)
}
#[test]
fn issue_2380_typed_body() {
report_problem_as(
indoc!(
r#"
F : F
a : F
a = 1
a
"#
),
indoc!(
r#"
CYCLIC ALIAS
The `F` alias is self-recursive in an invalid way:
1 F : F
^
Recursion in aliases is only allowed if recursion happens behind a
tag.
"#
),
)
}
#[test]
fn issue_2380_alias_with_vars() {
report_problem_as(
indoc!(
r#"
F a b : F a b
a : F Str Str
a
"#
),
indoc!(
r#"
CYCLIC ALIAS
The `F` alias is self-recursive in an invalid way:
1 F a b : F a b
^
Recursion in aliases is only allowed if recursion happens behind a
tag.
"#
),
)
}
} }