Edit Commentary/Compiler/TyingTheKnot authored by Edward Z. Yang's avatar Edward Z. Yang
...@@ -84,15 +84,18 @@ There are three parts to this: ...@@ -84,15 +84,18 @@ There are three parts to this:
The net effect is that at the point when we `loadDecls` the declarations, we have a list of `Name`s and unevaluated `TyThing` thunks, which we write into the global environment. Later, when we actually force the `TyThing` thunk, the suspended typechecking computation goes ahead and looks up the thunk in the environment, which has since been updated with the thunks we need. The net effect is that at the point when we `loadDecls` the declarations, we have a list of `Name`s and unevaluated `TyThing` thunks, which we write into the global environment. Later, when we actually force the `TyThing` thunk, the suspended typechecking computation goes ahead and looks up the thunk in the environment, which has since been updated with the thunks we need.
**Variation: tying the knot when typechecking mutually recursive interfaces.** Sometimes, recursive declarations can be spread out across several `hi` files (due to an `hs-boot` loop). In this case, laziness plays a similar role; however, instead of consulting per-interface mutable variable, the typechecking process consults the EPS (in the case of `ghc -c`) or the HPT (in the case of `ghc --make`). As before, laziness plays a critical role: we first add thunks representing all of the declarations to the EPS without doing any interface typechecking; then when we force the thunk the names can be found by doing the lookup. **Variation: tying the knot when typechecking mutually recursive interfaces.** Sometimes, recursive declarations can be spread out across several `hi` files (due to an `hs-boot` loop). In this case, laziness plays a similar role; however, instead of consulting per-interface mutable variable, the typechecking process consults the External Package State (EPS) (in the case of `ghc -c`) or the [Home Package Table (HPT)](commentary/compiler/driver) (in the case of `ghc --make`).
Something extra is needed in the case of `ghc --make`: when we typecheck against an `hs-boot` file, we may build `TyThing`s which refer to incomplete `TyThing`s provided from typechecking the `hs-boot` file. In this case, it's necessary to \*re-typecheck\* all of the interfaces in the module loop, so that they end up pointing to the most up-to-date `TyThing`s. The **external package state** is a mutable variable which stores a mapping from `Name`s to `TyThing`s for all the externally loaded symbols we know about (in the case of `ghc -c`, it also stores symbols from the home package as well). Whenever you call `loadInterface`, this variable is updated with the new symbols from that interface file. To tie the knot over multiple interface file, laziness plays a critical role: we first add thunks representing all of the declarations to the EPS without doing any interface typechecking for both interfaces; then when we force the thunk the names can be found by consulting the EPS.
The **home package table** is a mapping containing all of the `ModDetails` of modules from the home package which we are currently compiling. It's kept separate from the `EPS` because we often need to remove entities from this environment (e.g., we are in a GHCi session and we update a module.) Because these `ModDetails` are preserved from compilation to compilation (unlike `ghc -c`, where the EPS is reconstructed from scratch every invocation of GHC), we may have some `ModDetail`s in the HPT which are linked against incomplete `TyThing`s provided from typechecking an `hs-boot` file. Once we compile the real `hs` file for the `hs-boot` file, it's necessary to \*re-typecheck\* all of the interfaces in the module loop, so that they end up pointing to the most up-to-date `TyThing`s; this is done by simply throwing out all of the old `ModDetails` from the HPT and then reloading them back in. (`retypecheckLoop` in `GhcMake`).
## Tying the knot when typechecking a module ## Tying the knot when typechecking a module
As we typecheck Haskell source code, we produce `TyCon`s and other type-checking entities for them. If some declarations are mutually recursive, then we need to similarly tie the knot. There are two primary cases when this can occur: As we typecheck Haskell source code, we produce `TyCon`s and other type-checking entities. If some declarations are mutually recursive, then we need to similarly tie the knot. There are two primary cases when this can occur:
**A mutually recursive set of source declarations.** GHC simply arranges for every declaration in a mutually recursive set of declarations to be typechecked "all at once." For example, `tcTyClDecls` in `TcTyClsDecls` uses `fixM` to refer to the resulting type declarations, so they can be placed in the environment when we typecheck these very type declarations. **A mutually recursive set of source declarations.** GHC simply arranges for every declaration in a mutually recursive set of declarations to be typechecked "all at once." For example, `tcTyClDecls` in `TcTyClsDecls` uses `fixM` to refer to the resulting type declarations, so they can be placed in the environment when we typecheck these very type declarations.
... ...
......