mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-30 15:21:12 +00:00
Report self-recursive aliases at their declaration site, not in usages
Closes #2380
This commit is contained in:
parent
bec222b0c3
commit
48a3e871e8
4 changed files with 92 additions and 65 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(_) => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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.
|
||||||
|
"#
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue