Decouple L.H.S form GHC.Hs.Doc
TTG: Introduce Language.Haskell.Syntax.Doc*, decouple doc-string types from GHC.*
This MR addresses #26971 (closed)
Motivation
Remove three import edges from Language.Haskell.Syntax.* modules into GHC.* modules which violated the TTG architectural constraint that the Language.Haskell.Syntax sub-library must remain independent of GHC's internal modules:
| Module | Illegal import |
|---|---|
Language.Haskell.Syntax.ImpExp |
import {-# SOURCE #-} GHC.Hs.Doc (LHsDoc) |
Language.Haskell.Syntax.Type |
import GHC.Hs.Doc (LHsDoc) |
Language.Haskell.Syntax.Decls |
import GHC.Hs.Doc (LHsDoc, WithHsDocIdentifiers) |
The ImpExp case required a .hs-boot file (GHC.Hs.Doc.hs-boot) solely to break the resulting import cycle. This patch eliminates all three edges and removes the boot file by applying the TTG methodology to HsDocString and migrating the relevant types into a new Language.Haskell.Syntax.* module.
New module: Language.Haskell.Syntax.Doc
All "documetation string" definitions have been migrated to this module and redefined in terms of TTG extension points. There are now with no imports from the GHC.Hs.Doc* "namespace" within the L.H.S "namespace. The driving migration changes are listed below:
-- Extension families (instances in GHC.Hs.Extension.Pass and Haddock.Types)
type family XMultiLineDocString x
type family XNestedDocString x
type family XGeneratedDocString x
type family XXHsDocString x
-- TTG-parameterised docstring (migrated/redefined from GHC.Hs.DocString)
data HsDocString pass
= MultiLineDocString (XMultiLineDocString pass) !HsDocStringDecorator !(NonEmpty (LHsDocStringChunk pass))
| NestedDocString (XNestedDocString pass) !HsDocStringDecorator (LHsDocStringChunk pass)
| GeneratedDocString (XGeneratedDocString pass) HsDocStringChunk
| XHsDocString !(XXHsDocString pass)
-- Type aliases using XRec
type HsDoc pass = WithHsDocIdentifiers (HsDocString pass) pass
type LHsDoc pass = XRec pass (HsDoc pass)
type LHsDocStringChunk pass = XRec pass HsDocStringChunk
-- Moved from GHC.Hs.Doc; now uses LIdP instead of Located . IdP
data WithHsDocIdentifiers a pass = WithHsDocIdentifiers
{ hsDocString :: !a
, hsDocIdentifiers :: ![LIdP pass] -- was: ![Located (IdP pass)]
}
-- Pass-polymorphic constructor; takes HsDocStringChunk directly (no String encoding here)
mkGeneratedHsDocString :: XGeneratedDocString p -> HsDocStringChunk -> HsDocString p
-- Chunk types (moved from GHC.Hs.DocString)
newtype HsDocStringChunk = HsDocStringChunk ByteString
data HsDocStringDecorator = HsDocStringNext | HsDocStringPrevious | HsDocStringNamed !String | HsDocStringGroup !Int
mkHsDocStringChunkUtf8ByteString :: ByteString -> HsDocStringChunk
nullHDSC :: HsDocStringChunk -> Bool
Note that the following are are not exported from Language.Haskell.Syntax.Doc, as they they require string encoding functionality utf8EncodeByteString and utf8DecodeByteString from GHC.Utils.Encoding, and therefore live in GHC.Hs.DocString:
mkHsDocStringChunk :: String -> HsDocStringChunk
unpackHDSC :: HsDocStringChunk -> String
New module: GHC.Hs.Extension.Pass
This module splits GhcPass/Pass and their doc-string TTG instances out of GHC.Hs.Extension. This allows Language.Haskell.Syntax.Doc which needs XRec instances for GhcPass, but does not require all of GHC.Hs.Extension to define type-family instances. GHC.Hs.Extension re-exports GHC.Hs.Extension.Pass providing complete backwards compatibility of GHC.Hs.Extension's exposed API. Without splitting this module, the creation of a boot file would be necessary, which is more undesirable than a introducing small module that maintains backwards compatibility.
The following definitions have been migrated from GHC.Hs.Extension to GHC.Hs.Extension.Pass :
data GhcPass (c :: Pass) where GhcPs :: GhcPass 'Parsed; GhcRn :: GhcPass 'Renamed; GhcTc :: GhcPass 'Typechecked
data Pass = Parsed | Renamed | Typechecked
type instance XRec (GhcPass p) a = XRecGhc a
type XRecGhc a = GenLocated (Anno a) a
type instance Anno (HsDocString (GhcPass _)) = SrcSpan
type instance Anno HsDocStringChunk = SrcSpan
type instance XMultiLineDocString (GhcPass _) = NoExtField
type instance XNestedDocString (GhcPass _) = NoExtField
type instance XGeneratedDocString (GhcPass _) = NoExtField
type instance XXHsDocString (GhcPass _) = DataConCantHappen
Deleted: GHC.Hs/Doc.hs-boot
The boot file existed only to satisfy the {-# SOURCE #-} import in Language.Haskell.Syntax.ImpExp. With LHsDoc now defined in Language.Haskell.Syntax.Doc, the cycle no longer exists and the boot file is removed. Removing a boot file is always an achievement for celebration.
Notable GHC-layer changes
GHC.Hs.DocString: Updated to be GHC-pass-specific. HsDocString and LHsDocString become pass-parameterised:
type LHsDocString pass = XRec pass (HsDocString pass) -- was: Located HsDocString
-- String ↔ ByteString conversions now here (using GHC.Utils.Encoding)
mkHsDocStringChunk :: String -> HsDocStringChunk -- utf8EncodeByteString
unpackHDSC :: HsDocStringChunk -> String -- utf8DecodeByteString
-- GhcPass-specific constructor helper
mkGeneratedHsDocStringGhc :: String -> HsDocString (GhcPass p)
mkGeneratedHsDocStringGhc = mkGeneratedHsDocString noExtField . mkHsDocStringChunk
-- Pass-conversion coercions (identity on data, converts extension fields)
rnHsDocString :: HsDocString GhcPs -> HsDocString GhcRn
tcHsDocString :: HsDocString GhcRn -> HsDocString GhcTc
GHC.Rename.Doc: Distinguishes two rename operations that were previously conflated under rnHsDoc:
-- Renames docstring AND converts HsDocString GhcPs → GhcRn (for doc comments)
rnHsDoc :: HsDoc GhcPs -> RnM (HsDoc GhcRn)
-- Renames identifiers only, preserving the payload type (for warning texts)
rnHsDocIdentifiersOnly :: WithHsDocIdentifiers a GhcPs -> RnM (WithHsDocIdentifiers a GhcRn)
GHC.Rename.Module updated to call rnHsDocIdentifiersOnly for WarningTxt/DeprecatedTxt (which carry WithHsDocIdentifiers StringLiteral, not HsDocString).
GHC.Parser.HaddockLex.x: LHsDocStringChunk GhcPs replaces bare LHsDocStringChunk. The chunkBytes = utf8EncodeByteString . unpackHDSC round-trip (decode UTF-8 then re-encode it) is eliminated; plausibleIdents now pattern-matches directly on HsDocStringChunk bs.
GHC.HsToCore.Docs: unLoc s → unXRec @p s when extracting doc strings from DocCommentNext/DocCommentPrev, required because LHsDoc p = XRec p (HsDoc p) is no longer necessarily Located.
GHC.Iface.Ext.Ast: Instance head corrected from the illegal instance ToHie (LHsDoc GhcRn) (expands to a type-family application) to the concrete instance ToHie (Located (HsDoc GhcRn)), valid because Anno (HsDoc GhcRn) = SrcSpan.