Decoupling 'ModuleName' from GHC internals by adding a GHC-pass parameter to...

This MR is the final step in decoupling the L.H.S "namespace" from the GHC.* internals "namespace."

The final step

The driving change of the MR is adding a pass-polymorphic type parameter to the ModuleName data-type, allowing the AST user to specify the underlying representation of module names. Unsurprisingly, GHC will be using FastString as that representation. Hence the seminal change is converting the newtyped FastString to a data-type with Trees That Grow (TTG) extension points.

 -- | A ModuleName is essentially a simple string, e.g. @Data.List@.
-newtype ModuleName = ModuleName FastString deriving (Show, Eq)
+--
+-- The concrete representation of the string is selected by the consumer
+-- of the syntax tree.
+data ModuleNameP pass
+  = ModuleName !(XCModuleName pass)
+  | XModuleName !(XXModuleName pass)

The technical details

The data-type is renamed from ModuleName to ModuleNameP and a ModuleName = ModuleNameP GhcRn type-synonym is used on the GHC side to monomorphise the data-type and minimize the diff. If ModuleNameP was not used by any other part of the AST, the work would be over, but alas we have no such luck. The polymorphic type of ModuleNameP forces the data-types HsDoFlavourP, HsStmtContextP, and HsMatchContext to become pass-polymorphic as well. Handling the pass-polymorphic additions of these types makes the compatibility conversion on the GHC side much more delicate. Similar to ModuleName, type-synonym "punning" is used to maintain monomorphic types where possible throughout the GHC code-base, but monomorphism is not always possible with these forced changes.

The stumbling block

Once serious, unexpected architectural issue arose during the course of this MR; an import cycle.

GHC.Unit.Module.Name → GHC.Hs.Extension.Pass  (for GhcPass)
GHC.Hs.Extension.Pass → GHC.Utils.Panic / GHC.Types.SrcLoc   (Pass.hs:17-18)
GHC.Utils.Panic / SrcLoc → GHC.Utils.Outputable              (Panic.hs:56, SrcLoc.hs:121)
GHC.Utils.Outputable → GHC.Unit.Module.Name                  (Outputable.hs:129)

In order to resolve this cycle, a module splitting patch discussed for a different MR had to be applied here instead. This modules split takes the type-family instance definitions out of GHC.Hs.Extension.Pass leaving only the data-types, and moves the instances to a new module GHC.Hs.Extension.Instances, which imports the data-types that remained in GHC.Hs.Extension.Pass. The parent module GHC.Hs.Extension re-exports the contents of both modules. With this module split, GHC.Unit.Module.Name can import just the requisite data-types from GHC.Hs.Extension.Pass without also dragging in the type-family instances which cause the import cycle. This exact module split was pre-planned for a different MR which resolves orphan instances that resulted from the TTG project. The very same issue, requiring the only types and not the type-family instances, had limited the number of orphan type-class instances that could be resolved by #16299 (closed).

The result

Most files in this MR have only import changes which are in some way similar to this:

-import Language.Haskell.Syntax.Module.Name
+import GHC.Unit.Module.Name

Great care (perhaps too much time) has been taken to minimize the MR diff by using monomorphic type-synonyms wherever possible. In a few case, previously polymorphic function shave been made monomorphic in order to improve code-clarity and permit the usage of monomorphic type synonyms.

The TODO

  • Add a test case to the GHC test-suite to ensure that no L.H.S module imports a GHC.* internal module.
Edited by recursion-ninja

Merge request reports

Loading