tilia-0.0.1.0: src/Tilia/Render/Class.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
-- | Classes, instances and families.
--
-- What these have in common is a head followed by a body of declarations,
-- and a recurring difficulty: the syntax tree keeps the body's declarations
-- in several lists—signatures here, bindings there, associated families
-- somewhere else—so the order the author wrote them in survives only in
-- their spans. Every body in this module has to be put back in order before
-- it can be printed.
module Tilia.Render.Class
( classDecl,
clsInstDecl,
tyFamInstDecl,
dataFamInstDecl,
standaloneDerivDecl,
famDecl,
roleAnnot,
)
where
import Data.Function (on)
import Data.List (sortBy)
import Data.Maybe (isNothing)
import GHC.Builtin.Types (cTupleTyConName, isCTupleTyConName)
import GHC.Core.Coercion.Axiom (Role (..))
import GHC.Hs
import GHC.Types.Fixity (LexicalFixity (..))
import GHC.Types.Name.Reader (RdrName (..))
import GHC.Types.SrcLoc (GenLocated (..), leftmost_smallest, unLoc)
import Tilia.Doc.Combinators
import Tilia.Render.Context
import Tilia.Render.Data (dataDecl)
import Tilia.Render.Name
import Tilia.Render.Pragma
import Tilia.Render.Type
import Tilia.Span.Ghc
----------------------------------------------------------------------------
-- Classes
-- | A type class declaration.
classDecl ::
Ctx ->
AnnClassDecl ->
Maybe (LHsContext GhcPs) ->
LocatedN RdrName ->
LHsQTyVars GhcPs ->
LexicalFixity ->
[LHsFunDep GhcPs] ->
[LSig GhcPs] ->
LHsBinds GhcPs ->
[LFamilyDecl GhcPs] ->
[LTyFamDefltDecl GhcPs] ->
[LDocDecl GhcPs] ->
Doc
classDecl ctx anns ctxt tyCon HsQTvs {..} fixity fdeps sigs binds families defaults docs =
txt "class" <> layoutFrom ctx wholeHeadSpan head' <> body
where
headSpan = spanOf tyCon <> spansOf hsq_explicit
whereSpan = tokenSpan (acd_where anns)
wholeHeadSpan = foldMap spanOf ctxt <> headSpan <> spansOf fdeps
head' =
breakOrSpace
<> indent
( foldMap (classContext ctx) ctxt
<> layoutFrom ctx headSpan classHead
<> indent (funDeps ctx fdeps)
<> includeUnless
(null members)
(breakOrSpace <> keywordAt ctx whereSpan "where")
)
classHead
| isCTuple (unLoc tyCon) (length hsq_explicit) =
layoutWithin ctx (spanOf tyCon) (spansOf hsq_explicit) $
parens
( insideBrackets
(spanOf tyCon)
(commaSep (map (align . at_ ctx (tyVarBndr ctx)) hsq_explicit))
)
| otherwise =
defHead
(fixity == Infix)
True
(name ctx tyCon)
(map (at_ ctx (tyVarBndr ctx)) hsq_explicit)
body =
includeUnless
(null members)
(breakOrSpace <> indent (knotDeclsGrouped (ctxKnot ctx) ctx Associated members))
members =
inSourceOrder
[ map (fmap (SigD NoExtField)) sigs,
map (fmap (ValD NoExtField)) binds,
map (fmap (TyClD NoExtField . FamDecl NoExtField)) families,
map (fmap (InstD NoExtField . TyFamInstD NoExtField)) defaults,
map (fmap (DocD NoExtField)) docs
]
-- | Is this the constraint tuple of the given arity?
isCTuple :: RdrName -> Int -> Bool
isCTuple (Exact n) arity = isCTupleTyConName n && n == cTupleTyConName arity
isCTuple _ _ = False
-- | A context on a class head, with the @=>@ that follows it.
classContext :: Ctx -> LHsContext GhcPs -> Doc
classContext ctx ctxt
| null (unLoc ctxt) = mempty
| otherwise = context ctx ctxt <> joinedBy "=>"
-- | The functional dependencies of a class.
funDeps :: Ctx -> [LHsFunDep GhcPs] -> Doc
funDeps _ [] = mempty
funDeps ctx fdeps =
breakOrSpace
<> txt "|"
<> space
<> indent (commaSep (map (align . at_ ctx (funDep ctx)) fdeps))
funDep :: Ctx -> FunDep GhcPs -> Doc
funDep ctx (FunDep _ before after) =
hsep (map (name ctx) before)
<> space
<> txt "->"
<> space
<> hsep (map (name ctx) after)
----------------------------------------------------------------------------
-- Instances
-- | A class instance.
clsInstDecl :: Ctx -> ClsInstDecl GhcPs -> Doc
clsInstDecl ctx ClsInstDecl {cid_ext = (warning, anns, _), ..} =
txt "instance" <> layoutFrom ctx headSpan head' <> body
where
headSpan = foldMap spanOf warning <> spanOf cid_poly_ty
whereSpan = tokenSpan (acid_where anns)
head' =
foldMap (\w -> breakOrSpace <> at ctx w warningTxt) warning
<> breakOrSpace
<> at
ctx
cid_poly_ty
( \sigTy ->
indent $
foldMap (<> breakOrSpace) (overlapMode cid_overlap_mode)
<> hsSigTypeBody ctx sigTy
<> includeUnless
(null members)
(breakOrSpace <> keywordAt ctx whereSpan "where")
)
body =
includeUnless (null members) . indent $
breakOrSpace <> knotDeclsGrouped (ctxKnot ctx) ctx Associated members
members =
inSourceOrder
[ map (fmap (SigD NoExtField)) cid_sigs,
map (fmap (ValD NoExtField)) cid_binds,
map (fmap (InstD NoExtField . TyFamInstD NoExtField)) cid_tyfam_insts,
map (fmap (InstD NoExtField . DataFamInstD NoExtField)) cid_datafam_insts
]
-- | A standalone @deriving@ declaration.
standaloneDerivDecl :: Ctx -> DerivDecl GhcPs -> Doc
standaloneDerivDecl ctx DerivDecl {deriv_ext = (warning, _), ..} =
txt "deriving" <> space <> strategy
where
instanceHead indented =
indent $
txt "instance"
<> foldMap (\w -> breakOrSpace <> at ctx w warningTxt) warning
<> breakOrSpace
<> foldMap (<> breakOrSpace) (overlapMode deriv_overlap_mode)
<> nest (if indented then 1 else 0) (hsSigType ctx (hswc_body deriv_type))
strategy = case deriv_strategy of
Nothing -> instanceHead False
Just (L _ s) -> case s of
StockStrategy _ -> txt "stock " <> instanceHead False
AnyclassStrategy _ -> txt "anyclass " <> instanceHead False
NewtypeStrategy _ -> txt "newtype " <> instanceHead False
ViaStrategy (XViaStrategyPs _ sigTy) ->
txt "via"
<> breakOrSpace
<> indent (hsSigType ctx sigTy)
<> breakOrSpace
<> instanceHead True
-- | A type family instance.
tyFamInstDecl :: Ctx -> FamilyStyle -> TyFamInstDecl GhcPs -> Doc
tyFamInstDecl ctx style TyFamInstDecl {..} =
txt keyword <> breakOrSpace <> indent (tyFamInstEqn ctx tfid_eqn)
where
keyword = case style of
Associated -> "type"
Free -> "type instance"
-- | A data family instance.
dataFamInstDecl :: Ctx -> FamilyStyle -> DataFamInstDecl GhcPs -> Doc
dataFamInstDecl ctx style (DataFamInstDecl FamEqn {..}) =
dataDecl
ctx
style
feqn_tycon
feqn_pats
typeArgSpan
(typeArgument ctx)
feqn_fixity
outerBinders
feqn_rhs
where
-- @data instance forall k (a :: k). D a = …@ binds its variables ahead
-- of the head, exactly as a type family instance does.
outerBinders = case feqn_bndrs of
HsOuterImplicit NoExtField -> mempty
HsOuterExplicit _ bndrs ->
forallBndrs ctx Invisible (tyVarBndr ctx) bndrs <> breakOrSpace
----------------------------------------------------------------------------
-- Families
-- | A @data family@ or @type family@ declaration.
famDecl :: Ctx -> FamilyStyle -> FamilyDecl GhcPs -> Doc
famDecl ctx style FamilyDecl {fdTyVars = HsQTvs {..}, ..} =
txt keyword <> txt familyWord <> head' <> equations
where
(keyword, closedEqns) = case fdInfo of
DataFamily -> ("data", Nothing)
OpenTypeFamily -> ("type", Nothing)
ClosedTypeFamily eqs -> ("type", Just eqs)
familyWord = case style of
Associated -> ""
Free -> " family"
headSpan = spanOf fdLName <> spansOf hsq_explicit
headAndSigSpan = spanOf fdResultSig <> headSpan
head' =
indent . layoutFrom ctx headAndSigSpan $
breakOrSpace
<> layoutFrom
ctx
headSpan
( defHead
(fdFixity == Infix)
True
(name ctx fdLName)
(map (at_ ctx (tyVarBndr ctx)) hsq_explicit)
)
<> includeUnless
(isNothing resultSig && isNothing fdInjectivityAnn)
space
<> indent
( sequence_' resultSig
<> space
<> foldMap (at_ ctx (injectivityAnn ctx)) fdInjectivityAnn
)
sequence_' = maybe mempty id
resultSig = familyResultSig ctx fdResultSig
equations = case closedEqns of
Nothing -> mempty
Just eqs ->
indent (layoutFrom ctx headAndSigSpan (breakOrSpace <> txt "where"))
<> case eqs of
-- @where ..@ is how a closed family says that its equations are
-- not being given here.
Nothing -> space <> txt ".."
-- A closed family may be given no equations at all, and then
-- the @where@ is the whole of it. Breaking the line anyway
-- leaves the next thing along hanging under a @where@ that
-- opened a block nothing was put in.
Just given ->
includeUnless (null given) $
hardBreak <> indent (vsep (map (at_ ctx (tyFamInstEqn ctx)) given))
familyResultSig :: Ctx -> LFamilyResultSig GhcPs -> Maybe Doc
familyResultSig ctx (L _ sig) = case sig of
NoSig NoExtField -> Nothing
KindSig NoExtField k ->
Just (txt "::" <> breakOrSpace <> hsType ctx k)
TyVarSig NoExtField bndr ->
Just (txt "=" <> breakOrSpace <> at ctx bndr (tyVarBndr ctx))
injectivityAnn :: Ctx -> InjectivityAnn GhcPs -> Doc
injectivityAnn ctx (InjectivityAnn _ from to) =
txt "|"
<> space
<> name ctx from
<> space
<> txt "->"
<> space
<> hsep (map (name ctx) to)
-- | One equation of a type family.
tyFamInstEqn :: Ctx -> TyFamInstEqn GhcPs -> Doc
tyFamInstEqn ctx FamEqn {..} =
binders <> nest (if hasBinders then 1 else 0) (lhs <> rhs)
where
(binders, hasBinders) = case feqn_bndrs of
HsOuterImplicit NoExtField -> (mempty, False)
HsOuterExplicit _ bndrs ->
( forallBndrs ctx Invisible (tyVarBndr ctx) bndrs <> breakOrSpace,
not (null bndrs)
)
lhs =
layoutFrom ctx (spanOf feqn_tycon <> foldMap typeArgSpan feqn_pats) $
defHead
(feqn_fixity == Infix)
True
(name ctx feqn_tycon)
(map (typeArgument ctx) feqn_pats)
rhs =
indent (joinedBy "=" <> hsType ctx feqn_rhs)
----------------------------------------------------------------------------
-- Role annotations
-- | A @type role@ declaration.
roleAnnot :: Ctx -> RoleAnnotDecl GhcPs -> Doc
roleAnnot ctx (RoleAnnotDecl _ tyCon roles) =
txt "type role"
<> breakOrSpace
<> indent
( name ctx tyCon
<> breakOrSpace
<> indent (align (sepBy breakOrSpace (map (align . at_ ctx role) roles)))
)
where
role = maybe (txt "_") $ \case
Nominal -> txt "nominal"
Representational -> txt "representational"
Phantom -> txt "phantom"
----------------------------------------------------------------------------
-- Helpers
-- | Merge several lists of declarations back into the order they were
-- written in.
inSourceOrder :: [[LHsDecl GhcPs]] -> [LHsDecl GhcPs]
inSourceOrder = sortBy (leftmost_smallest `on` getLocA) . concat