`AnnotationReferences` ignored the fact that within an annotation
the same symbol could be referenced both in a qualified and unqualified
manner.
By using `References` we will properly track all the possible combinations.
We were still passing `ModuleIds` from `load` to `can`, but now
that imports can appear in any scope, we don't know which package
an unqualified module name belongs to from the top level.
We now pass `PackageModuleIds` instead and keep a Map of `ModuleName` to
`ModuleId` in `Scope`.
This also allow us to import multiple modules with the same name from different
packages as long as a unique alias is provided.
Moves handling of ingested file imports from load to can, so that they
can be properly introduced in the scope they appear.
Example:
import "input.txt" as input : Str
image =
import "image.png" as bytes : List U8
# `bytes` is only available under `image`
decodePng bytes
...
Now that imports can be limited to smaller scopes than the entire module,
unused import warnings need to work like unused def warnings.
This commit moves unused import warnings discovery and reporting from load
to canonicalization where we can track their usage per scope.
This also fixes a longstanding bug where unused exposed names from an import
were not reported if they were only used in a qualified manner.
After parsing a module, we now recursively traverse the tree to find
all imports inside Defs, not just the top-level ones.
Previously, imported modules were available in the entire file,
but that's no longer the case. Therefore, Scope now keeps track of
imported modules and Env::qualified_lookup checks whether a module
is available in the provided scope.
Note: Unused import warnings are still global and need to be updated.
Previously, all imports were available in the header, so we could start
processing dependencies as soon as we parsed it. However, the new imports
are treated as defs, so we have to parse the whole module to find them.
This commit essentially moves the dependency resolution from the `LoadHeader`
phase to the `Parse` phase, and it updates canonicalization to introduce
module symbols into scope when a `ValueDef::ModuleImport` is encountered.
NOTE:
- The `imports` header still parses, but it's no longer wired up. I will remove
it in an upcoming commit.
- Ingested files and imports that appear in nested expressions are not
yet supported by load
With a code like
```
thenDo = \x, callback ->
callback x
f = \{} ->
code = 10u16
bf = \{} ->
thenDo code \_ -> bf {}
bf {}
```
The lambda `\_ -> bf {}` must capture `bf`. Previously, this would not
happen correctly, because we assumed that mutually recursive functions
(including singleton recursive functions, like `bf` here) cannot capture
themselves.
Of course, that premise does not hold in general. Instead, we should have
mutually recursive functions capture the closure (haha, get it) of
values captured by all functions constituting the mutual recursion.
Then, any nested closures can capture outer recursive closures' values
appropriately.
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!
"###
);
```