Module Resolution
Recall that import targets are specified by their local names only. It is perfectly legal that there should be many discoverable modules that share the same local name. Yet it is essential that an import target should be unambiguously understood, lest the wrong module be selected to satisfy an import.
Module resolution is the process by which import targets are resolved to concrete modules. Intuitively, and in brief, module resolution proceeds as follows:
- If the import target is in the same directory as the dependent module, then choose it.
- Otherwise, look in the enclosing directory. If the import target is there, then choose it.
- Keep looking upward through the enclosing directories until reaching a module root. If the import target is discovered along the way, then choose it.
- If the import target is directly inside the same module root as recursively includes the dependent module, then choose it.
- Otherwise, scan through all the module roots in the order that they were provided in the module roots path. Only look directly, not recursively, in each module root. If the import target is found, then choose it.
- If the import target was found, but it refers to a package, then reach inside and choose the representative.
- If the import target couldn't be found, then fail the module resolution process.
Understanding the procedure at the abbreviated level of detail given above is adequate for the vast majority of use cases. It should generally prove safe to skip ahead to the conclusion and example.
Following is a detailed description of the module resolution algorithm: [1]
- Canonize the import target.
- Module resolution begins by naively canonizing the import target — specified by the local name
L
— as if it were a sibling of the dependent module. A file is sibling to another if they both reside in the same file system directory. Let us call this naive canonical nameM
. - Is there an applicable rename rule?
- Does the module renames file specify a module rename rule that would transform
M
into the canonical nameM′
? If so, then apply the rename rule. If not, then go ahead with translating the canonical name to a file system path. - Apply the rename rule.
- Apply the module rename rule to transform
M
into the canonical nameM′
. - Translate the canonical name into a file path.
- Consider the expansion of the canonical name
M
(orM′
if a module rename rule was applied),
whereM
≝
/R/P1/P2/…/Pn/L
,R
is a module root name,P1
throughPn
are local names of packages, andL
is the local name of the import target. To translateM
to a file path,- Replace
R
with its source module path, denoted byS
. - Rewrite
P1
asP1.avail
. - Rewrite
P2
asP2.avail
. - …
- Rewrite
Pn
asPn.avail
. - Rewrite
L
asL.avail
. - Replace each occurrence of solidus
/
(U+002F)
with the platform-specific directory separator. (On Unix this will be solidus; on Windows, reverse solidus\
(U+005C)
.)
F
, whose expansion isF ≝ /S/P1.avail/P2.avail/…/Pn.avail/L.avail
. - Replace
- Does the file path contain any packages?
F
may not include any packages (either because it never included any or because they have all been rejected and removed), so check to see if it does. If so, then see if it references an existing file. If not, thenL.avail
resides directly within a root.- The file path contains packages, but does it exist?
F
(orF′
,F″
, …, if some packages have already been dropped) includes at least one package,Pi
,
DoesF′ ≝ /S/…/Pi.avail/L.avail
.F
denote an existing file? If so, then determine whetherF
is a package. If not, then dropPn
.- Drop the rightmost package from the file path.
- Derive
F′
fromF
by dropping the rightmost package,Pn
. The expansion ofF′
is therefore
Now see ifF′ ≝ /S/P1.avail/P2.avail/…/Pn-1.avail/L.avail
.F′
includes any packages. - The file path contains no packages. Does it exist?
- Since
F
does not contains any local names that correspond to packages, the expansion ofF
is given by
DoesF ≝ /S/L.avail
.F
denote an existing file? If so, then determine whetherF
is a package. If not, then determine whether there are any unexplored module roots. - Are some module roots still unexplored?
- With respect to the resolution of
M
, are some module roots specified on the module root path still unexplored? If so, then replaceS
withS′
. If not, then module resolution has failed; the system will emit an appropriate error message and then halt. - Try the next unexplored module root.
- Recall that the module roots path is the list of all module roots that participate in module discovery. The module roots are necessarily ordered, because all text is ordered. Derive
S′
by choosing the leftmost module root from the module roots path that has not already been searched. Then deriveF′
such that
Now determine whetherF ≝ /S′/L.avail
.F′
exists. - Does the file path refer to a package?
- Does
F
refer to a directory rather than a file? If so, thenF
refers to a package, so deriveF′
fromF
such that
and verify that its representative exists. If not, then module resolution has succeeded:F′ ≝ /S/P1/P2/…/Pn/L.avail/L.avail
,F
is the unambiguous resolution ofM
. - The file path refers to a package representative, but does it exist?
F
denotes a package representative. DoesF
exists? If so, then module resolution has succeeded:F
is the unambiguous resolution ofM
. If not, then module resolution has failed; the system will emit an appropriate error message and then halt.
An extremely important practical consequence of this procedure is that the contents of a package are always impenetrable to a module that resides outside of that package. This ensures that a module representative is the exclusive gateway to services offered by modules located recursively within its package. This is an essential concept of Avail's encapsulation model.
Take Wump the Wumpus
, one of the official Avail case studies available in the examples
module root, as the scaffolding for some use cases of module resolution:
Entries beginning with a solidus denote ordinary directories, and just serve to give context to the module roots. Entries encased in plus signs +
(U+002B)
denote module roots; assume that the two shown here, avail
and examples
, are the only ones specified by the module roots path, and that the occur in the order given. Entries ending with a solidus denote module packages. Entries ending with an asterisk *
(U+002A)
denote module package representatives. All other entries are ordinary modules. Assume that the module renames file is empty.
Peering into /examples/Wump the Wumpus/Game/Command/Parser
, we find this module header:
Let us consider each of the import targets in turn.
- Of course, we already know that
Avail
is the standard library. But the system doesn't know it immediately when it encounters this import target. It just knows that it should try to resolve the local nameAvail
to an actual module. It starts by canonizing the name to:It then translates this canonical name to the following file path:/examples/Wump the Wumpus/Game/Command/AvailIt then recursively scans the enclosing directories, from the inside out, looking for a file name/usr/local/avail/src/examples/Wump the Wumpus.avail/Game.avail/Command.avail/Avail.availAvail.avail
:Having exhausted the/usr/local/avail/src/examples/Wump the Wumpus.avail/Game.avail/Command.avail/Avail.avail /usr/local/avail/src/examples/Wump the Wumpus.avail/Game.avail/Avail.avail /usr/local/avail/src/examples/Wump the Wumpus.avail/Avail.avail /usr/local/avail/src/examples/Avail.availexamples
module root, it next rewrites the file path to be relative to the leftmost unexplored module root,avail
:This file path refers to a package, so append the name of the package representative,/usr/local/avail/src/avail/Avail.availAvail.avail
. The local nameAvail
is ultimately resolved to/usr/local/avail/src/avail/Avail.avail/Avail.avail
. - The local name
Definers
is canonized to:And then translated to the file path:/examples/Wump the Wumpus/Game/Command/DefinersThis file exists — it is sitting right beside/usr/local/avail/src/examples/Wump the Wumpus.avail/Game.avail/Command.avail/Definers.availParser.avail
— so resolution succeeds. - To find
IO
, the system tries the following file paths:This file exists, so resolution succeeds./usr/local/avail/src/examples/Wump the Wumpus.avail/Game.avail/Command.avail/IO.avail /usr/local/avail/src/examples/Wump the Wumpus.avail/Game.avail/IO.avail /usr/local/avail/src/examples/Wump the Wumpus.avail/IO.avail - Finally,
Scanner
is trivially resolved to:/usr/local/avail/src/examples/Wump the Wumpus.avail/Game.avail/Command.avail/Scanner.avail
[1] Technically, the system is only guaranteed to carry out an algorithm that is behaviorally isomorphic to this one: it is guaranteed that module resolution produces only results that are consistent with the description of the algorithm provided here. The implementation may actually use different techniques, generally because they are more efficient.
‹ Module Renaming | | | Return to Modules | | | Module Bodies › |