Provide warning for defs that are used only in (mutual) recursion

This patch provides errors for defs that are used only in
possibly-mutual recursion, and are not reachable outside of their
recursive closures. For example:

```
test_report!(
    mutual_recursion_not_reached_nested,
    indoc!(
        r#"
        app "test" provides [main] to "./platform"

        main =
            f = \{} -> if Bool.true then "" else g {}
            g = \{} -> if Bool.true then "" else f {}
            ""
        "#
    ),
@r###"
── DEFINITIONs ONLY USED IN RECURSION ──────────────────── /code/proj/Main.roc ─

These 2 definitions are only used in mutual recursion with themselves:

4│>      f = \{} -> if Bool.true then "" else g {}
5│>      g = \{} -> if Bool.true then "" else f {}

If you don't intend to use or export any of them, they should all be
removed!
"###
);
```
This commit is contained in:
Ayaz Hafiz 2022-12-01 18:26:01 -06:00
parent 1beb00f490
commit 0a807dc43e
No known key found for this signature in database
GPG key ID: 0E2A37416A25EF58
8 changed files with 262 additions and 15 deletions

View file

@ -200,6 +200,7 @@ impl Params {
scc: Sccs {
matrix: ReferenceMatrix::new(length),
components: 0,
not_initial: BitVec::repeat(false, length),
},
scca: BitVec::repeat(false, length),
}
@ -253,6 +254,10 @@ fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) {
}
}
if !params.s.is_empty() {
params.scc.not_initial.set(params.scc.components, true);
}
params.scc.components += 1;
}
}
@ -261,6 +266,7 @@ fn recurse_onto(length: usize, bitvec: &BitVec, v: usize, params: &mut Params) {
pub struct Sccs {
components: usize,
matrix: ReferenceMatrix,
not_initial: BitVec,
}
impl Sccs {
@ -271,7 +277,7 @@ impl Sccs {
///
/// It is guaranteed that a group is non-empty, and that flattening the groups gives a valid
/// topological ordering.
pub fn groups(&self) -> std::iter::Take<bitvec::slice::ChunksExact<'_, Element, Order>> {
pub fn groups(&self) -> impl DoubleEndedIterator<Item = (&'_ BitSlice, bool)> {
// work around a panic when requesting a chunk size of 0
let length = if self.matrix.length == 0 {
// the `.take(self.components)` ensures the resulting iterator will be empty
@ -286,13 +292,15 @@ impl Sccs {
.bitvec
.chunks_exact(length)
.take(self.components)
.enumerate()
.map(|(c, slice)| (slice, !self.not_initial[c]))
}
/// Reorder the input slice based on the SCCs. This produces a topological sort
pub fn reorder<T>(&self, slice: &mut [T]) {
debug_assert_eq!(self.matrix.length, slice.len());
let mut indices: Vec<_> = self.groups().flat_map(|s| s.iter_ones()).collect();
let mut indices: Vec<_> = self.groups().flat_map(|(s, _)| s.iter_ones()).collect();
for i in 0..slice.len() {
let mut index = indices[i];