{-# LANGUAGE CPP #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE KindSignatures #-} module Language.Haskell.Brittany.Internal.Layouters.DataDecl ( layoutDataDecl ) where import Language.Haskell.Brittany.Internal.Prelude import Language.Haskell.Brittany.Internal.PreludeUtils import qualified Control.Monad.Reader.Class as Reader.Class import qualified Control.Monad.RWS.Class as RWS.Class import qualified Control.Monad.State.Class as State.Class import qualified Control.Monad.Trans.Except as ExceptT import qualified Control.Monad.Trans.MultiRWS.Lazy as MultiRWSL import qualified Control.Monad.Trans.MultiRWS.Strict as MultiRWSS import qualified Control.Monad.Trans.State as State import qualified Control.Monad.Trans.State.Lazy as StateL import qualified Control.Monad.Trans.State.Strict as StateS import qualified Control.Monad.Writer.Class as Writer.Class import qualified Data.Bool as Bool import qualified Data.ByteString import qualified Data.ByteString as ByteString import qualified Data.ByteString.Char8 import qualified Data.ByteString.Lazy as ByteStringL import qualified Data.Coerce import qualified Data.Data import qualified Data.Either import qualified Data.Foldable import qualified Data.Foldable as Foldable import qualified Data.IntMap.Lazy as IntMapL import qualified Data.IntMap.Strict as IntMapS import qualified Data.List.Extra import qualified Data.Map as Map import qualified Data.Maybe import qualified Data.Semigroup as Semigroup import qualified Data.Sequence as Seq import qualified Data.Set as Set import qualified Data.Strict.Maybe as Strict import qualified Data.Text as Text import qualified Data.Text.Encoding as Text.Encoding import qualified Data.Text.IO as Text.IO import qualified Data.Text.Lazy as TextL import qualified Data.Text.Lazy.Encoding as TextL.Encoding import qualified Data.Text.Lazy.IO as TextL.IO import qualified GHC.OldList as List import qualified Safe as Safe import qualified System.Directory import qualified System.IO import qualified Text.PrettyPrint import qualified Text.PrettyPrint.Annotated import qualified Text.PrettyPrint.Annotated.HughesPJ import qualified Text.PrettyPrint.Annotated.HughesPJClass import Language.Haskell.Brittany.Internal.Types import Language.Haskell.Brittany.Internal.LayouterBasics import Language.Haskell.Brittany.Internal.Config.Types import GHC.Types.Name.Reader ( RdrName(..) ) import GHC ( Located, runGhc, GenLocated(L), moduleNameString ) import qualified GHC import GHC.Hs import GHC.Types.Name import GHC.Types.Basic import Language.Haskell.GHC.ExactPrint.Types ( mkAnnKey ) import Language.Haskell.Brittany.Internal.Layouters.Type import {-# SOURCE #-} Language.Haskell.Brittany.Internal.Layouters.Expr import {-# SOURCE #-} Language.Haskell.Brittany.Internal.Layouters.Stmt import Language.Haskell.Brittany.Internal.Layouters.Pattern import Language.Haskell.Brittany.Internal.Utils import GHC.Data.Bag ( mapBagM ) layoutDataDecl :: Located (TyClDecl GhcPs) -> Located RdrName -> LHsQTyVars GhcPs -> HsDataDefn GhcPs -> ToBriDocM BriDocNumbered layoutDataDecl ltycl name (HsQTvs _ bndrs) defn = case defn of -- newtype MyType a b = MyType .. HsDataDefn _ext NewType (L _ []) _ctype Nothing [cons] mDerivs -> case cons of (L _ (ConDeclH98 _ext consName (L _ False) _qvars (Just (L _ [])) details _conDoc)) -> docWrapNode ltycl $ do nameStr <- lrdrNameToTextAnn name consNameStr <- lrdrNameToTextAnn consName tyVarLine <- fmap return $ createBndrDoc bndrs -- headDoc <- fmap return $ docSeq -- [ appSep $ docLitS "newtype") -- , appSep $ docLit nameStr -- , appSep tyVarLine -- ] rhsDoc <- fmap return $ createDetailsDoc consNameStr details createDerivingPar mDerivs $ docSeq [ appSep $ docLitS "newtype" , appSep $ docLit nameStr , appSep tyVarLine , docSeparator , docLitS "=" , docSeparator , rhsDoc ] _ -> briDocByExactNoComment ltycl -- data MyData a b -- (zero constructors) HsDataDefn _ext DataType (L _ lhsContext) _ctype Nothing [] mDerivs -> docWrapNode ltycl $ do lhsContextDoc <- docSharedWrapper createContextDoc lhsContext nameStr <- lrdrNameToTextAnn name tyVarLine <- fmap return $ createBndrDoc bndrs createDerivingPar mDerivs $ docSeq [ appSep $ docLitS "data" , lhsContextDoc , appSep $ docLit nameStr , appSep tyVarLine ] -- data MyData = MyData .. -- data MyData = MyData { .. } HsDataDefn _ext DataType (L _ lhsContext) _ctype Nothing [cons] mDerivs -> case cons of (L _ (ConDeclH98 _ext consName (L _ _hasExt) qvars mRhsContext details _conDoc)) -> docWrapNode ltycl $ do lhsContextDoc <- docSharedWrapper createContextDoc lhsContext nameStr <- lrdrNameToTextAnn name consNameStr <- lrdrNameToTextAnn consName tyVarLine <- fmap return $ createBndrDoc bndrs forallDocMay <- case createForallDoc qvars of Nothing -> pure Nothing Just x -> Just . pure <$> x rhsContextDocMay <- case mRhsContext of Nothing -> pure Nothing Just (L _ ctxt) -> Just . pure <$> createContextDoc ctxt rhsDoc <- fmap return $ createDetailsDoc consNameStr details consDoc <- fmap pure $ docNonBottomSpacing $ case (forallDocMay, rhsContextDocMay) of (Just forallDoc, Just rhsContextDoc) -> docLines [ docSeq [docLitS "=", docSeparator, docForceSingleline forallDoc] , docSeq [ docLitS "." , docSeparator , docSetBaseY $ docLines [rhsContextDoc, docSetBaseY rhsDoc] ] ] (Just forallDoc, Nothing) -> docLines [ docSeq [docLitS "=", docSeparator, docForceSingleline forallDoc] , docSeq [docLitS ".", docSeparator, rhsDoc] ] (Nothing, Just rhsContextDoc) -> docSeq [ docLitS "=" , docSeparator , docSetBaseY $ docLines [rhsContextDoc, docSetBaseY rhsDoc] ] (Nothing, Nothing) -> docSeq [docLitS "=", docSeparator, rhsDoc] createDerivingPar mDerivs $ docAlt [ -- data D = forall a . Show a => D a docSeq [ docNodeAnnKW ltycl (Just GHC.AnnData) $ docSeq [ appSep $ docLitS "data" , docForceSingleline $ lhsContextDoc , appSep $ docLit nameStr , appSep tyVarLine , docSeparator ] , docLitS "=" , docSeparator , docSetIndentLevel $ docSeq [ case forallDocMay of Nothing -> docEmpty Just forallDoc -> docSeq [ docForceSingleline forallDoc , docSeparator , docLitS "." , docSeparator ] , maybe docEmpty docForceSingleline rhsContextDocMay , rhsDoc ] ] , -- data D -- = forall a . Show a => D a docAddBaseY BrIndentRegular $ docPar ( docNodeAnnKW ltycl (Just GHC.AnnData) $ docSeq [ appSep $ docLitS "data" , docForceSingleline lhsContextDoc , appSep $ docLit nameStr , tyVarLine ] ) ( docSeq [ docLitS "=" , docSeparator , docSetIndentLevel $ docSeq [ case forallDocMay of Nothing -> docEmpty Just forallDoc -> docSeq [ docForceSingleline forallDoc , docSeparator , docLitS "." , docSeparator ] , maybe docEmpty docForceSingleline rhsContextDocMay , rhsDoc ] ] ) , -- data D -- = forall a -- . Show a => -- D a docAddBaseY BrIndentRegular $ docPar ( docNodeAnnKW ltycl (Just GHC.AnnData) $ docSeq [ appSep $ docLitS "data" , docForceSingleline lhsContextDoc , appSep $ docLit nameStr , tyVarLine ] ) consDoc , -- data -- Show a => -- D -- = forall a -- . Show a => -- D a -- This alternative is only for -XDatatypeContexts. -- But I think it is rather unlikely this will trigger without -- -XDataTypeContexts, especially with the `docNonBottomSpacing` -- above, so while not strictly necessary, this should not -- hurt. docAddBaseY BrIndentRegular $ docPar (docLitS "data") ( docLines [ lhsContextDoc , docNodeAnnKW ltycl (Just GHC.AnnData) $ docSeq [ appSep $ docLit nameStr , tyVarLine ] , consDoc ] ) ] _ -> briDocByExactNoComment ltycl _ -> briDocByExactNoComment ltycl createContextDoc :: HsContext GhcPs -> ToBriDocM BriDocNumbered createContextDoc [] = docEmpty createContextDoc [t] = docSeq [layoutType t, docSeparator, docLitS "=>", docSeparator] createContextDoc (t1 : tR) = do t1Doc <- docSharedWrapper layoutType t1 tRDocs <- tR `forM` docSharedWrapper layoutType docAlt [ docSeq [ docLitS "(" , docForceSingleline $ docSeq $ List.intersperse docCommaSep (t1Doc : tRDocs) , docLitS ") =>" , docSeparator ] , docLines $ join [ [docSeq [docLitS "(", docSeparator, t1Doc]] , tRDocs <&> \tRDoc -> docSeq [docLitS ",", docSeparator, tRDoc] , [docLitS ") =>", docSeparator] ] ] createBndrDoc :: [LHsTyVarBndr flag GhcPs] -> ToBriDocM BriDocNumbered createBndrDoc bs = do tyVarDocs <- bs `forM` \case (L _ (UserTyVar _ _ext vname)) -> return $ (lrdrNameToText vname, Nothing) (L _ (KindedTyVar _ _ext lrdrName kind)) -> do d <- docSharedWrapper layoutType kind return $ (lrdrNameToText lrdrName, Just $ d) docSeq $ List.intersperse docSeparator $ tyVarDocs <&> \(vname, mKind) -> case mKind of Nothing -> docLit vname Just kind -> docSeq [ docLitS "(" , docLit vname , docSeparator , docLitS "::" , docSeparator , kind , docLitS ")" ] createDerivingPar :: HsDeriving GhcPs -> ToBriDocM BriDocNumbered -> ToBriDocM BriDocNumbered createDerivingPar derivs mainDoc = do case derivs of (L _ []) -> mainDoc (L _ types) -> docPar mainDoc $ docEnsureIndent BrIndentRegular $ docLines $ docWrapNode derivs $ derivingClauseDoc <$> types derivingClauseDoc :: LHsDerivingClause GhcPs -> ToBriDocM BriDocNumbered derivingClauseDoc (L _ (HsDerivingClause _ext mStrategy types)) = case types of (L _ []) -> docSeq [] (L _ ts) -> let tsLength = length ts whenMoreThan1Type val = if tsLength > 1 then docLitS val else docLitS "" (lhsStrategy, rhsStrategy) = maybe (docEmpty, docEmpty) strategyLeftRight mStrategy in docSeq [ docDeriving , docWrapNodePrior types $ lhsStrategy , docSeparator , whenMoreThan1Type "(" , docWrapNodeRest types $ docSeq $ List.intersperse docCommaSep $ ts <&> \case HsIB _ t -> layoutType t , whenMoreThan1Type ")" , rhsStrategy ] where strategyLeftRight = \case (L _ StockStrategy ) -> (docLitS " stock", docEmpty) (L _ AnyclassStrategy ) -> (docLitS " anyclass", docEmpty) (L _ NewtypeStrategy ) -> (docLitS " newtype", docEmpty) lVia@(L _ (ViaStrategy viaTypes) ) -> ( docEmpty , case viaTypes of HsIB _ext t -> docSeq [ docWrapNode lVia $ docLitS " via" , docSeparator , layoutType t ] ) docDeriving :: ToBriDocM BriDocNumbered docDeriving = docLitS "deriving" createDetailsDoc :: Text -> HsConDeclDetails GhcPs -> (ToBriDocM BriDocNumbered) createDetailsDoc consNameStr details = case details of PrefixCon args -> do indentPolicy <- mAsk <&> _conf_layout .> _lconfig_indentPolicy .> confUnpack let singleLine = docSeq [ docLit consNameStr , docSeparator , docForceSingleline $ docSeq $ List.intersperse docSeparator $ fmap hsScaledThing args <&> layoutType ] leftIndented = docSetParSpacing . docAddBaseY BrIndentRegular . docPar (docLit consNameStr) . docLines $ layoutType <$> fmap hsScaledThing args multiAppended = docSeq [ docLit consNameStr , docSeparator , docSetBaseY $ docLines $ layoutType <$> fmap hsScaledThing args ] multiIndented = docSetBaseY $ docAddBaseY BrIndentRegular $ docPar (docLit consNameStr) (docLines $ layoutType <$> fmap hsScaledThing args) case indentPolicy of IndentPolicyLeft -> docAlt [singleLine, leftIndented] IndentPolicyMultiple -> docAlt [singleLine, multiAppended, leftIndented] IndentPolicyFree -> docAlt [singleLine, multiAppended, multiIndented, leftIndented] RecCon (L _ []) -> docSeq [docLit consNameStr, docSeparator, docLit $ Text.pack "{}"] RecCon lRec@(L _ fields@(_:_)) -> do let ((fName1, fType1) : fDocR) = mkFieldDocs fields -- allowSingleline <- mAsk <&> _conf_layout .> _lconfig_allowSinglelineRecord .> confUnpack let allowSingleline = False docAddBaseY BrIndentRegular $ runFilteredAlternative $ do -- single-line: { i :: Int, b :: Bool } addAlternativeCond allowSingleline $ docSeq [ docLit consNameStr , docSeparator , docWrapNodePrior lRec $ docLitS "{" , docSeparator , docWrapNodeRest lRec $ docForceSingleline $ docSeq $ join $ [fName1, docSeparator, docLitS "::", docSeparator, fType1] : [ [ docLitS "," , docSeparator , fName , docSeparator , docLitS "::" , docSeparator , fType ] | (fName, fType) <- fDocR ] , docSeparator , docLitS "}" ] addAlternative $ docPar (docLit consNameStr) (docWrapNodePrior lRec $ docNonBottomSpacingS $ docLines [ docAlt [ docCols ColRecDecl [ appSep (docLitS "{") , appSep $ docForceSingleline fName1 , docSeq [docLitS "::", docSeparator] , docForceSingleline $ fType1 ] , docSeq [ docLitS "{" , docSeparator , docSetBaseY $ docAddBaseY BrIndentRegular $ docPar fName1 (docSeq [docLitS "::", docSeparator, fType1]) ] ] , docWrapNodeRest lRec $ docLines $ fDocR <&> \(fName, fType) -> docAlt [ docCols ColRecDecl [ docCommaSep , appSep $ docForceSingleline fName , docSeq [docLitS "::", docSeparator] , docForceSingleline fType ] , docSeq [ docLitS "," , docSeparator , docSetBaseY $ docAddBaseY BrIndentRegular $ docPar fName (docSeq [docLitS "::", docSeparator, fType]) ] ] , docLitS "}" ] ) InfixCon arg1 arg2 -> docSeq [ layoutType $ hsScaledThing arg1 , docSeparator , docLit consNameStr , docSeparator , layoutType $ hsScaledThing arg2 ] where mkFieldDocs :: [LConDeclField GhcPs] -> [(ToBriDocM BriDocNumbered, ToBriDocM BriDocNumbered)] mkFieldDocs = fmap $ \lField -> case lField of L _ (ConDeclField _ext names t _) -> createNamesAndTypeDoc lField names t createForallDoc :: [LHsTyVarBndr flag GhcPs] -> Maybe (ToBriDocM BriDocNumbered) createForallDoc [] = Nothing createForallDoc lhsTyVarBndrs = Just $ docSeq [docLitS "forall ", createBndrDoc lhsTyVarBndrs] createNamesAndTypeDoc :: Data.Data.Data ast => Located ast -> [GenLocated t (FieldOcc GhcPs)] -> Located (HsType GhcPs) -> (ToBriDocM BriDocNumbered, ToBriDocM BriDocNumbered) createNamesAndTypeDoc lField names t = ( docNodeAnnKW lField Nothing $ docWrapNodePrior lField $ docSeq [ docSeq $ List.intersperse docCommaSep $ names <&> \case L _ (FieldOcc _ fieldName) -> docLit =<< lrdrNameToTextAnn fieldName ] , docWrapNodeRest lField $ layoutType t )