Exact Print Annotations in GHC

Exact Printing

0x01, 001, 0_01
{-# Language CPP #-}
{-# LANGUAGE CPP #-}

For Tooling

foo xxx = let a = 1
              b = 2
          in xxx + a + b
foo xxxlonger = let a = 1
                    b = 2
                in xxxlonger + a + b

Changes (GHC 9.2)

  • The Exact Print Annotations are now in-tree
  • ghc-exactprint is still the library to programme against
  • This has been updated heavily for use with GHC 9.2.1

ghc-exactprint changes

  • Indirect tie-up of API Annotations is gone
  • We no longer have a two-phased approach
  • We no longer need to add default annotations

Aside: AST

  • Language.Haskell.Syntax
  • GHC.Hs
type ParsedSource = Located HsModule

XRec

type LHsExpr p = XRec p (HsExpr p)
type family XRec p a = r | r -> a
type instance XRec (GhcPass p) a = GenLocated (Anno a) a
type family Anno a = b
type instance Anno (HsExpr (GhcPass p)) = SrcSpanAnnA

XRec example

Credit to Shayne Fletcher

- LHsExpr GhcPs
- XRec GhcPs (HsExpr GhcPs)
- GenLocated (Anno (HsExpr GhcPs)) (HsExpr GhcPs)
- GenLocated SrcSpanAnnA (HsExpr GhcPs)
- LocatedA (HsExpr GhcPs))

Expanding further we have

- GenLocated SrcSpanAnnA (HsExpr GhcPs)
- GenLocated (SrcAnn AnnListItem) (HsExpr GhcPs)
- GenLocated (SrcSpanAnn' (EpAnn AnnListItem)) (HsExpr GhcPs)

GHC Dev Survival Guide

All is not lost.

A is your friend

type instance Anno (HsExpr (GhcPass p)) = SrcSpanAnnA
type LocatedA = GenLocated SrcSpanAnnA
Located    => LocatedA
getLoc     => getLocA
noLoc      => noLocA
locA -- get SrcSpan from GenLocated payload
setSrcSpan => setSrcSpanA
addLocM    => addLocMA

Changes: ghc-exactprint types

data Annotation = Ann -- old ghc-exactprint type
  {
    -- interfacing up into the AST
    annEntryDelta      :: !DeltaPos
  , annPriorComments   :: ![(Comment,  DeltaPos)]
  , annFollowingComments   :: ![(Comment,  DeltaPos)]
  -- interfacing down into the AST
  , annsDP             :: ![(KeywordId, DeltaPos)]
  , annSortKey         :: !(Maybe [GHC.RealSrcSpan])
  }
data EpAnn ann
  = EpAnn { entry   :: Anchor
          , anns     :: ann
          , comments :: EpAnnComments
          }
  | EpAnnNotUsed
data Anchor = Anchor { anchor :: RealSrcSpan
                     , anchor_op :: AnchorOperation }
data AnchorOperation = UnchangedAnchor
                     | MovedAnchor DeltaPos
newtype DeltaPos = DP (Int,Int)
data DeltaPos
  = SameLine { deltaColumn :: !Int }
  | DifferentLine
      { deltaLine   :: !Int, -- ^ deltaLine should always be > 0
        deltaColumn :: !Int
      }
data EpaLocation = EpaSpan RealSrcSpan
                 | EpaDelta DeltaPos

Annotation

| HsLet (XLet p)
        (HsLocalBinds p)
        (LHsExpr  p)
type instance XLet GhcPs = EpAnn AnnsLet
data AnnsLet = AnnsLet { alLet :: EpaLocation,
                         alIn  :: EpaLocation }

Code Modifications

doAddLocal = do
  (d1:d2:d3) <- hsDecls lp
  balanceComments d1 d2
  (d1',_) <- modifyValD (getLoc d1) d1 $ \_m d -> do
    return ((newDecl : d),Nothing)
  replaceDecls lp [d1', d2, d3]
doAddLocal = do
  (de1:d2:d3:_) <- hsDecls lp
  (de1'',d2') <- balanceComments de1 d2
  (de1',_) <- modifyValD (getLocA de1'') de1'' $ \_m d -> do
    return ((wrapDecl decl' : d),Nothing)
  replaceDecls lp [de1', d2', d3]

Summary

  • The API Annotations have become Exact Print Annotations
  • And have moved into the GHC source tree as first class citizens
  • This has some benefits
    • We have tests of actual usage
    • All the pieces are in one repository
  • The major benefit is it now allows incremental improvement, and removal of rough edges.

The work is incomplete, changes to come (master)

https://gitlab.haskell.org/ghc/ghc/-/issues/20039

Resources

https://gitlab.haskell.org/ghc/ghc/-/wikis/api-annotations https://github.com/alanz/ghc-exactprint/tree/ghc-9.2 https://blog.shaynefletcher.org/2021/05/annotations-in-ghc.html