{-# LANGUAGE DataKinds #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE BangPatterns #-} {-# LANGUAGE ViewPatterns #-} module Language.Haskell.Brittany.Internal.Layouters.Decl ( layoutDecl , layoutSig , layoutBind , layoutLocalBinds , layoutGuardLStmt , layoutPatternBind , layoutGrhs , layoutPatternBindFinal ) where #include "prelude.inc" import Language.Haskell.Brittany.Internal.Types import Language.Haskell.Brittany.Internal.LayouterBasics import Language.Haskell.Brittany.Internal.Config.Types import Language.Haskell.Brittany.Internal.Layouters.Type import qualified Language.Haskell.GHC.ExactPrint as ExactPrint import qualified Language.Haskell.GHC.ExactPrint.Types as ExactPrint import Language.Haskell.Brittany.Internal.ExactPrintUtils import Language.Haskell.Brittany.Internal.Utils import GHC ( runGhc , GenLocated(L) , moduleNameString , AnnKeywordId(..) ) import SrcLoc ( SrcSpan, noSrcSpan, Located , getLoc, unLoc ) import qualified FastString #if MIN_VERSION_ghc(8,10,1) /* ghc-8.10.1 */ import GHC.Hs import GHC.Hs.Extension (NoExtField (..)) #else import HsSyn import HsExtension (NoExt (..)) #endif import Name import BasicTypes ( InlinePragma(..) , Activation(..) , InlineSpec(..) , RuleMatchInfo(..) , LexicalFixity(..) ) 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.Layouters.DataDecl import Bag ( mapBagM, bagToList, emptyBag ) import Data.Char (isUpper) layoutDecl :: ToBriDoc HsDecl layoutDecl d@(L loc decl) = case decl of SigD _ sig -> withTransformedAnns d $ layoutSig (L loc sig) ValD _ bind -> withTransformedAnns d $ layoutBind (L loc bind) >>= \case Left ns -> docLines $ return <$> ns Right n -> return n TyClD _ tycl -> withTransformedAnns d $ layoutTyCl (L loc tycl) InstD _ (TyFamInstD _ tfid) -> withTransformedAnns d $ layoutTyFamInstDecl False d tfid InstD _ (ClsInstD _ inst) -> withTransformedAnns d $ layoutClsInst (L loc inst) _ -> briDocByExactNoComment d -------------------------------------------------------------------------------- -- Sig -------------------------------------------------------------------------------- layoutSig :: ToBriDoc Sig layoutSig lsig@(L _loc sig) = case sig of TypeSig _ names (HsWC _ (HsIB _ typ)) -> layoutNamesAndType Nothing names typ InlineSig _ name (InlinePragma _ spec _arity phaseAct conlike) -> docWrapNode lsig $ do nameStr <- lrdrNameToTextAnn name specStr <- specStringCompat lsig spec let phaseStr = case phaseAct of NeverActive -> "" -- not [] - for NOINLINE NeverActive is -- in fact the default AlwaysActive -> "" ActiveBefore _ i -> "[~" ++ show i ++ "] " ActiveAfter _ i -> "[" ++ show i ++ "] " let conlikeStr = case conlike of FunLike -> "" ConLike -> "CONLIKE " docLit $ Text.pack ("{-# " ++ specStr ++ conlikeStr ++ phaseStr) <> nameStr <> Text.pack " #-}" ClassOpSig _ False names (HsIB _ typ) -> layoutNamesAndType Nothing names typ PatSynSig _ names (HsIB _ typ) -> layoutNamesAndType (Just "pattern") names typ _ -> briDocByExactNoComment lsig -- TODO where layoutNamesAndType mKeyword names typ = docWrapNode lsig $ do let keyDoc = case mKeyword of Just key -> [appSep . docLit $ Text.pack key] Nothing -> [] nameStrs <- names `forM` lrdrNameToTextAnn let nameStr = Text.intercalate (Text.pack ", ") $ nameStrs typeDoc <- docSharedWrapper layoutType typ hasComments <- hasAnyCommentsBelow lsig shouldBeHanging <- mAsk <&> _conf_layout .> _lconfig_hangingTypeSignature .> confUnpack if shouldBeHanging then docSeq $ [ appSep $ docWrapNodeRest lsig $ docSeq $ keyDoc <> [docLit nameStr] , docSetBaseY $ docLines [ docCols ColTyOpPrefix [ docLit $ Text.pack ":: " , docAddBaseY (BrIndentSpecial 3) $ typeDoc ] ] ] else layoutLhsAndType hasComments (appSep . docWrapNodeRest lsig . docSeq $ keyDoc <> [docLit nameStr]) "::" typeDoc specStringCompat :: MonadMultiWriter [BrittanyError] m => LSig GhcPs -> InlineSpec -> m String specStringCompat ast = \case NoUserInline -> mTell [ErrorUnknownNode "NoUserInline" ast] $> "" Inline -> pure "INLINE " Inlinable -> pure "INLINABLE " NoInline -> pure "NOINLINE " layoutGuardLStmt :: ToBriDoc' (Stmt GhcPs (LHsExpr GhcPs)) layoutGuardLStmt lgstmt@(L _ stmtLR) = docWrapNode lgstmt $ case stmtLR of BodyStmt _ body _ _ -> layoutExpr body BindStmt _ lPat expr _ _ -> do patDoc <- docSharedWrapper layoutPat lPat expDoc <- docSharedWrapper layoutExpr expr docCols ColBindStmt [ appSep $ colsWrapPat =<< patDoc , docSeq [appSep $ docLit $ Text.pack "<-", expDoc] ] _ -> unknownNodeError "" lgstmt -- TODO -------------------------------------------------------------------------------- -- HsBind -------------------------------------------------------------------------------- layoutBind :: ToBriDocC (HsBindLR GhcPs GhcPs) (Either [BriDocNumbered] BriDocNumbered) layoutBind lbind@(L _ bind) = case bind of FunBind _ fId (MG _ lmatches@(L _ matches) _) _ [] -> do idStr <- lrdrNameToTextAnn fId binderDoc <- docLit $ Text.pack "=" funcPatDocs <- docWrapNode lbind $ docWrapNode lmatches $ layoutPatternBind (Just idStr) binderDoc `mapM` matches return $ Left $ funcPatDocs PatBind _ pat (GRHSs _ grhss whereBinds) ([], []) -> do patDocs <- colsWrapPat =<< layoutPat pat clauseDocs <- layoutGrhs `mapM` grhss mWhereDocs <- layoutLocalBinds whereBinds let mWhereArg = mWhereDocs <&> \d -> (mkAnnKey lbind, d) -- TODO: is this the right AnnKey? binderDoc <- docLit $ Text.pack "=" hasComments <- hasAnyCommentsBelow lbind fmap Right $ docWrapNode lbind $ layoutPatternBindFinal Nothing binderDoc (Just patDocs) clauseDocs mWhereArg hasComments #if MIN_VERSION_ghc(8,8,0) PatSynBind _ (PSB _ patID lpat rpat dir) -> do #else PatSynBind _ (PSB _ patID lpat rpat dir) -> do #endif fmap Right $ docWrapNode lbind $ layoutPatSynBind patID lpat dir rpat _ -> Right <$> unknownNodeError "" lbind layoutIPBind :: ToBriDoc IPBind layoutIPBind lipbind@(L _ bind) = case bind of XIPBind{} -> unknownNodeError "XIPBind" lipbind IPBind _ (Right _) _ -> error "brittany internal error: IPBind Right" IPBind _ (Left (L _ (HsIPName name))) expr -> do ipName <- docLit $ Text.pack $ '?' : FastString.unpackFS name binderDoc <- docLit $ Text.pack "=" exprDoc <- layoutExpr expr hasComments <- hasAnyCommentsBelow lipbind layoutPatternBindFinal Nothing binderDoc (Just ipName) [([], exprDoc, expr)] Nothing hasComments data BagBindOrSig = BagBind (LHsBindLR GhcPs GhcPs) | BagSig (LSig GhcPs) bindOrSigtoSrcSpan :: BagBindOrSig -> SrcSpan bindOrSigtoSrcSpan (BagBind (L l _)) = l bindOrSigtoSrcSpan (BagSig (L l _)) = l layoutLocalBinds :: ToBriDocC (HsLocalBindsLR GhcPs GhcPs) (Maybe [BriDocNumbered]) layoutLocalBinds lbinds@(L _ binds) = case binds of -- HsValBinds (ValBindsIn lhsBindsLR []) -> -- Just . (>>= either id return) . Data.Foldable.toList <$> mapBagM layoutBind lhsBindsLR -- TODO: fix ordering -- x@(HsValBinds (ValBindsIn{})) -> -- Just . (:[]) <$> unknownNodeError "HsValBinds (ValBindsIn _ (_:_))" x HsValBinds _ (ValBinds _ bindlrs sigs) -> do let unordered = [ BagBind b | b <- Data.Foldable.toList bindlrs ] ++ [ BagSig s | s <- sigs ] ordered = sortBy (comparing bindOrSigtoSrcSpan) unordered docs <- docWrapNode lbinds $ join <$> ordered `forM` \case BagBind b -> either id return <$> layoutBind b BagSig s -> return <$> layoutSig s return $ Just $ docs -- x@(HsValBinds (ValBindsOut _binds _lsigs)) -> HsValBinds _ (XValBindsLR{}) -> error "brittany internal error: XValBindsLR" XHsLocalBindsLR{} -> error "brittany internal error: XHsLocalBindsLR" x@(HsIPBinds _ XHsIPBinds{}) -> Just . (: []) <$> unknownNodeError "XHsIPBinds" (L noSrcSpan x) HsIPBinds _ (IPBinds _ bb) -> Just <$> mapM layoutIPBind bb EmptyLocalBinds{} -> return $ Nothing -- TODO: we don't need the `LHsExpr GhcPs` anymore, now that there is -- parSpacing stuff.B layoutGrhs :: LGRHS GhcPs (LHsExpr GhcPs) -> ToBriDocM ([BriDocNumbered], BriDocNumbered, LHsExpr GhcPs) layoutGrhs lgrhs@(L _ (GRHS _ guards body)) = do guardDocs <- docWrapNode lgrhs $ layoutStmt `mapM` guards bodyDoc <- layoutExpr body return (guardDocs, bodyDoc, body) layoutGrhs (L _ (XGRHS{})) = error "brittany internal error: XGRHS" layoutPatternBind :: Maybe Text -> BriDocNumbered -> LMatch GhcPs (LHsExpr GhcPs) -> ToBriDocM BriDocNumbered layoutPatternBind funId binderDoc lmatch@(L _ match) = do let pats = m_pats match let (GRHSs _ grhss whereBinds) = m_grhss match patDocs <- pats `forM` \p -> fmap return $ colsWrapPat =<< layoutPat p let isInfix = isInfixMatch match mIdStr <- case match of Match _ (FunRhs matchId _ _) _ _ -> Just <$> lrdrNameToTextAnn matchId _ -> pure Nothing let mIdStr' = fixPatternBindIdentifier match <$> mIdStr patDoc <- docWrapNodePrior lmatch $ case (mIdStr', patDocs) of (Just idStr, p1:p2:pr) | isInfix -> if null pr then docCols ColPatternsFuncInfix [ appSep $ docForceSingleline p1 , appSep $ docLit $ idStr , docForceSingleline p2 ] else docCols ColPatternsFuncInfix ( [docCols ColPatterns [ docParenL , appSep $ docForceSingleline p1 , appSep $ docLit $ idStr , docForceSingleline p2 , appSep $ docParenR ] ] ++ (spacifyDocs $ docForceSingleline <$> pr) ) (Just idStr, []) -> docLit idStr (Just idStr, ps) -> docCols ColPatternsFuncPrefix $ appSep (docLit $ idStr) : (spacifyDocs $ docForceSingleline <$> ps) (Nothing, ps) -> docCols ColPatterns $ (List.intersperse docSeparator $ docForceSingleline <$> ps) clauseDocs <- docWrapNodeRest lmatch $ layoutGrhs `mapM` grhss mWhereDocs <- layoutLocalBinds whereBinds let mWhereArg = mWhereDocs <&> \d -> (mkAnnKey lmatch, d) let alignmentToken = if null pats then Nothing else funId hasComments <- hasAnyCommentsBelow lmatch layoutPatternBindFinal alignmentToken binderDoc (Just patDoc) clauseDocs mWhereArg hasComments fixPatternBindIdentifier :: Match GhcPs (LHsExpr GhcPs) -> Text -> Text fixPatternBindIdentifier match idStr = go $ m_ctxt match where go = \case (FunRhs _ _ SrcLazy ) -> Text.cons '~' idStr (FunRhs _ _ SrcStrict ) -> Text.cons '!' idStr (FunRhs _ _ NoSrcStrict) -> idStr (StmtCtxt ctx1 ) -> goInner ctx1 _ -> idStr -- I have really no idea if this path ever occurs, but better safe than -- risking another "drop bangpatterns" bugs. goInner = \case (PatGuard ctx1) -> go ctx1 (ParStmtCtxt ctx1) -> goInner ctx1 (TransStmtCtxt ctx1) -> goInner ctx1 _ -> idStr layoutPatternBindFinal :: Maybe Text -> BriDocNumbered -> Maybe BriDocNumbered -> [([BriDocNumbered], BriDocNumbered, LHsExpr GhcPs)] -> Maybe (ExactPrint.AnnKey, [BriDocNumbered]) -- ^ AnnKey for the node that contains the AnnWhere position annotation -> Bool -> ToBriDocM BriDocNumbered layoutPatternBindFinal alignmentToken binderDoc mPatDoc clauseDocs mWhereDocs hasComments = do let patPartInline = case mPatDoc of Nothing -> [] Just patDoc -> [appSep $ docForceSingleline $ return patDoc] patPartParWrap = case mPatDoc of Nothing -> id Just patDoc -> docPar (return patDoc) whereIndent <- do shouldSpecial <- mAsk <&> _conf_layout .> _lconfig_indentWhereSpecial .> confUnpack regularIndentAmount <- mAsk <&> _conf_layout .> _lconfig_indentAmount .> confUnpack pure $ if shouldSpecial then BrIndentSpecial (max 1 (regularIndentAmount `div` 2)) else BrIndentRegular -- TODO: apart from this, there probably are more nodes below which could -- be shared between alternatives. wherePartMultiLine :: [ToBriDocM BriDocNumbered] <- case mWhereDocs of Nothing -> return $ [] Just (annKeyWhere, [w]) -> fmap (pure . pure) $ docAlt [ docEnsureIndent BrIndentRegular $ docSeq [ docLit $ Text.pack "where" , docSeparator , docForceSingleline $ return w ] , docMoveToKWDP annKeyWhere AnnWhere False $ docEnsureIndent whereIndent $ docLines [ docLit $ Text.pack "where" , docEnsureIndent whereIndent $ docSetIndentLevel $ docNonBottomSpacing $ return w ] ] Just (annKeyWhere, ws) -> fmap (pure . pure) $ docMoveToKWDP annKeyWhere AnnWhere False $ docEnsureIndent whereIndent $ docLines [ docLit $ Text.pack "where" , docEnsureIndent whereIndent $ docSetIndentLevel $ docNonBottomSpacing $ docLines $ return <$> ws ] let singleLineGuardsDoc guards = appSep $ case guards of [] -> docEmpty [g] -> docSeq [appSep $ docLit $ Text.pack "|", docForceSingleline $ return g] gs -> docSeq $ [appSep $ docLit $ Text.pack "|"] ++ (List.intersperse docCommaSep (docForceSingleline . return <$> gs) ) wherePart = case mWhereDocs of Nothing -> Just docEmpty Just (_, [w]) -> Just $ docSeq [ docSeparator , appSep $ docLit $ Text.pack "where" , docSetIndentLevel $ docForceSingleline $ return w ] _ -> Nothing indentPolicy <- mAsk <&> _conf_layout .> _lconfig_indentPolicy .> confUnpack runFilteredAlternative $ do case clauseDocs of [(guards, body, _bodyRaw)] -> do let guardPart = singleLineGuardsDoc guards forM_ wherePart $ \wherePart' -> -- one-line solution addAlternativeCond (not hasComments) $ docCols (ColBindingLine alignmentToken) [ docSeq (patPartInline ++ [guardPart]) , docSeq [ appSep $ return binderDoc , docForceSingleline $ return body , wherePart' ] ] -- one-line solution + where in next line(s) addAlternativeCond (Data.Maybe.isJust mWhereDocs) $ docLines $ [ docCols (ColBindingLine alignmentToken) [ docSeq (patPartInline ++ [guardPart]) , docSeq [ appSep $ return binderDoc , docForceParSpacing $ docAddBaseY BrIndentRegular $ return body ] ] ] ++ wherePartMultiLine -- two-line solution + where in next line(s) addAlternative $ docLines $ [ docForceSingleline $ docSeq (patPartInline ++ [guardPart, return binderDoc]) , docEnsureIndent BrIndentRegular $ docForceSingleline $ return body ] ++ wherePartMultiLine -- pattern and exactly one clause in single line, body as par; -- where in following lines addAlternative $ docLines $ [ docCols (ColBindingLine alignmentToken) [ docSeq (patPartInline ++ [guardPart]) , docSeq [ appSep $ return binderDoc , docForceParSpacing $ docAddBaseY BrIndentRegular $ return body ] ] ] -- , lineMod $ docAlt -- [ docSetBaseY $ return body -- , docAddBaseY BrIndentRegular $ return body -- ] ++ wherePartMultiLine -- pattern and exactly one clause in single line, body in new line. addAlternative $ docLines $ [ docSeq (patPartInline ++ [guardPart, return binderDoc]) , docNonBottomSpacing $ docEnsureIndent BrIndentRegular $ docAddBaseY BrIndentRegular $ return body ] ++ wherePartMultiLine _ -> return () -- no alternatives exclusively when `length clauseDocs /= 1` case mPatDoc of Nothing -> return () Just patDoc -> -- multiple clauses added in-paragraph, each in a single line -- example: foo | bar = baz -- | lll = asd addAlternativeCond (indentPolicy == IndentPolicyFree) $ docLines $ [ docSeq [ appSep $ docForceSingleline $ return patDoc , docSetBaseY $ docLines $ clauseDocs <&> \(guardDocs, bodyDoc, _) -> do let guardPart = singleLineGuardsDoc guardDocs -- the docForceSingleline might seems superflous, but it -- helps the alternative resolving impl. docForceSingleline $ docCols ColGuardedBody [ guardPart , docSeq [ appSep $ return binderDoc , docForceSingleline $ return bodyDoc -- i am not sure if there is a benefit to using -- docForceParSpacing additionally here: -- , docAddBaseY BrIndentRegular $ return bodyDoc ] ] ] ] ++ wherePartMultiLine -- multiple clauses, each in a separate, single line addAlternative $ docLines $ [ docAddBaseY BrIndentRegular $ patPartParWrap $ docLines $ map docSetBaseY $ clauseDocs <&> \(guardDocs, bodyDoc, _) -> do let guardPart = singleLineGuardsDoc guardDocs -- the docForceSingleline might seems superflous, but it -- helps the alternative resolving impl. docForceSingleline $ docCols ColGuardedBody [ guardPart , docSeq [ appSep $ return binderDoc , docForceSingleline $ return bodyDoc -- i am not sure if there is a benefit to using -- docForceParSpacing additionally here: -- , docAddBaseY BrIndentRegular $ return bodyDoc ] ] ] ++ wherePartMultiLine -- multiple clauses, each with the guard(s) in a single line, body -- as a paragraph addAlternative $ docLines $ [ docAddBaseY BrIndentRegular $ patPartParWrap $ docLines $ map docSetBaseY $ clauseDocs <&> \(guardDocs, bodyDoc, _) -> docSeq $ ( case guardDocs of [] -> [] [g] -> [ docForceSingleline $ docSeq [appSep $ docLit $ Text.pack "|", return g] ] gs -> [ docForceSingleline $ docSeq $ [appSep $ docLit $ Text.pack "|"] ++ List.intersperse docCommaSep (return <$> gs) ] ) ++ [ docSeparator , docCols ColOpPrefix [ appSep $ return binderDoc , docAddBaseY BrIndentRegular $ docForceParSpacing $ return bodyDoc ] ] ] ++ wherePartMultiLine -- multiple clauses, each with the guard(s) in a single line, body -- in a new line as a paragraph addAlternative $ docLines $ [ docAddBaseY BrIndentRegular $ patPartParWrap $ docLines $ map docSetBaseY $ clauseDocs >>= \(guardDocs, bodyDoc, _) -> ( case guardDocs of [] -> [] [g] -> [ docForceSingleline $ docSeq [appSep $ docLit $ Text.pack "|", return g] ] gs -> [ docForceSingleline $ docSeq $ [appSep $ docLit $ Text.pack "|"] ++ List.intersperse docCommaSep (return <$> gs) ] ) ++ [ docCols ColOpPrefix [ appSep $ return binderDoc , docAddBaseY BrIndentRegular $ docForceParSpacing $ return bodyDoc ] ] ] ++ wherePartMultiLine -- conservative approach: everything starts on the left. addAlternative $ docLines $ [ docAddBaseY BrIndentRegular $ patPartParWrap $ docLines $ map docSetBaseY $ clauseDocs >>= \(guardDocs, bodyDoc, _) -> ( case guardDocs of [] -> [] [g] -> [docSeq [appSep $ docLit $ Text.pack "|", return g]] (g1:gr) -> ( docSeq [appSep $ docLit $ Text.pack "|", return g1] : ( gr <&> \g -> docSeq [appSep $ docLit $ Text.pack ",", return g] ) ) ) ++ [ docCols ColOpPrefix [ appSep $ return binderDoc , docAddBaseY BrIndentRegular $ return bodyDoc ] ] ] ++ wherePartMultiLine -- | Layout a pattern synonym binding layoutPatSynBind :: Located (IdP GhcPs) -> HsPatSynDetails (Located (IdP GhcPs)) -> HsPatSynDir GhcPs -> LPat GhcPs -> ToBriDocM BriDocNumbered layoutPatSynBind name patSynDetails patDir rpat = do let patDoc = docLit $ Text.pack "pattern" binderDoc = case patDir of ImplicitBidirectional -> docLit $ Text.pack "=" _ -> docLit $ Text.pack "<-" body = colsWrapPat =<< layoutPat rpat whereDoc = docLit $ Text.pack "where" mWhereDocs <- layoutPatSynWhere patDir headDoc <- fmap pure $ docSeq $ [ patDoc , docSeparator , layoutLPatSyn name patSynDetails , docSeparator , binderDoc ] runFilteredAlternative $ do addAlternative $ -- pattern .. where -- .. -- .. docAddBaseY BrIndentRegular $ docSeq ( [headDoc, docSeparator, body] ++ case mWhereDocs of Just ds -> [docSeparator, docPar whereDoc (docLines ds)] Nothing -> [] ) addAlternative $ -- pattern .. = -- .. -- pattern .. <- -- .. where -- .. -- .. docAddBaseY BrIndentRegular $ docPar headDoc (case mWhereDocs of Nothing -> body Just ds -> docLines ([ docSeq [body, docSeparator, whereDoc] ] ++ ds) ) -- | Helper method for the left hand side of a pattern synonym layoutLPatSyn :: Located (IdP GhcPs) -> HsPatSynDetails (Located (IdP GhcPs)) -> ToBriDocM BriDocNumbered layoutLPatSyn name (PrefixCon vars) = do docName <- lrdrNameToTextAnn name names <- mapM lrdrNameToTextAnn vars docSeq . fmap appSep $ docLit docName : (docLit <$> names) layoutLPatSyn name (InfixCon left right) = do leftDoc <- lrdrNameToTextAnn left docName <- lrdrNameToTextAnn name rightDoc <- lrdrNameToTextAnn right docSeq . fmap (appSep . docLit) $ [leftDoc, docName, rightDoc] layoutLPatSyn name (RecCon recArgs) = do docName <- lrdrNameToTextAnn name args <- mapM (lrdrNameToTextAnn . recordPatSynSelectorId) recArgs docSeq . fmap docLit $ [docName, Text.pack " { " ] <> intersperse (Text.pack ", ") args <> [Text.pack " }"] -- | Helper method to get the where clause from of explicitly bidirectional -- pattern synonyms layoutPatSynWhere :: HsPatSynDir GhcPs -> ToBriDocM (Maybe [ToBriDocM BriDocNumbered]) layoutPatSynWhere hs = case hs of ExplicitBidirectional (MG _ (L _ lbinds) _) -> do binderDoc <- docLit $ Text.pack "=" Just <$> mapM (docSharedWrapper $ layoutPatternBind Nothing binderDoc) lbinds _ -> pure Nothing -------------------------------------------------------------------------------- -- TyClDecl -------------------------------------------------------------------------------- layoutTyCl :: ToBriDoc TyClDecl layoutTyCl ltycl@(L _loc tycl) = case tycl of SynDecl _ name vars fixity typ -> do let isInfix = case fixity of Prefix -> False Infix -> True -- hasTrailingParen <- hasAnnKeywordComment ltycl AnnCloseP -- let parenWrapper = if hasTrailingParen -- then appSep . docWrapNodeRest ltycl -- else id let wrapNodeRest = docWrapNodeRest ltycl docWrapNodePrior ltycl $ layoutSynDecl isInfix wrapNodeRest name (hsq_explicit vars) typ DataDecl _ext name tyVars _ dataDefn -> layoutDataDecl ltycl name tyVars dataDefn _ -> briDocByExactNoComment ltycl layoutSynDecl :: Bool -> (ToBriDocM BriDocNumbered -> ToBriDocM BriDocNumbered) -> Located (IdP GhcPs) -> [LHsTyVarBndr GhcPs] -> LHsType GhcPs -> ToBriDocM BriDocNumbered layoutSynDecl isInfix wrapNodeRest name vars typ = do nameStr <- lrdrNameToTextAnn name let lhs = appSep . wrapNodeRest $ if isInfix then do let (a : b : rest) = vars hasOwnParens <- hasAnnKeywordComment a AnnOpenP -- This isn't quite right, but does give syntactically valid results let needsParens = not (null rest) || hasOwnParens docSeq $ [ docLit $ Text.pack "type" , docSeparator ] ++ [ docParenL | needsParens ] ++ [ layoutTyVarBndr False a , docSeparator , docLit nameStr , docSeparator , layoutTyVarBndr False b ] ++ [ docParenR | needsParens ] ++ fmap (layoutTyVarBndr True) rest else docSeq $ [ docLit $ Text.pack "type" , docSeparator , docWrapNode name $ docLit nameStr ] ++ fmap (layoutTyVarBndr True) vars sharedLhs <- docSharedWrapper id lhs typeDoc <- docSharedWrapper layoutType typ hasComments <- hasAnyCommentsConnected typ layoutLhsAndType hasComments sharedLhs "=" typeDoc layoutTyVarBndr :: Bool -> ToBriDoc HsTyVarBndr layoutTyVarBndr needsSep lbndr@(L _ bndr) = do docWrapNodePrior lbndr $ case bndr of XTyVarBndr{} -> error "brittany internal error: XTyVarBndr" UserTyVar _ name -> do nameStr <- lrdrNameToTextAnn name docSeq $ [docSeparator | needsSep] ++ [docLit nameStr] KindedTyVar _ name kind -> do nameStr <- lrdrNameToTextAnn name docSeq $ [ docSeparator | needsSep ] ++ [ docLit $ Text.pack "(" , appSep $ docLit nameStr , appSep . docLit $ Text.pack "::" , docForceSingleline $ layoutType kind , docLit $ Text.pack ")" ] -------------------------------------------------------------------------------- -- TyFamInstDecl -------------------------------------------------------------------------------- layoutTyFamInstDecl :: Data.Data.Data a => Bool -> Located a -> TyFamInstDecl GhcPs -> ToBriDocM BriDocNumbered layoutTyFamInstDecl inClass outerNode tfid = do let #if MIN_VERSION_ghc(8,8,0) FamEqn _ name bndrsMay pats _fixity typ = hsib_body $ tfid_eqn tfid -- bndrsMay isJust e.g. with -- type instance forall a . MyType (Maybe a) = Either () a innerNode = outerNode #else FamEqn _ name pats _fixity typ = hsib_body $ tfid_eqn tfid bndrsMay = Nothing innerNode = outerNode #endif docWrapNodePrior outerNode $ do nameStr <- lrdrNameToTextAnn name needsParens <- hasAnnKeyword outerNode AnnOpenP let instanceDoc = if inClass then docLit $ Text.pack "type" else docSeq [appSep . docLit $ Text.pack "type", docLit $ Text.pack "instance"] makeForallDoc :: [LHsTyVarBndr GhcPs] -> ToBriDocM BriDocNumbered makeForallDoc bndrs = do bndrDocs <- layoutTyVarBndrs bndrs docSeq ( [docLit (Text.pack "forall")] ++ processTyVarBndrsSingleline bndrDocs ) lhs = docWrapNode innerNode . docSeq $ [appSep instanceDoc] ++ [ makeForallDoc foralls | Just foralls <- [bndrsMay] ] ++ [ docParenL | needsParens ] ++ [appSep $ docWrapNode name $ docLit nameStr] ++ intersperse docSeparator (layoutHsTyPats pats) ++ [ docParenR | needsParens ] hasComments <- (||) <$> hasAnyRegularCommentsConnected outerNode <*> hasAnyRegularCommentsRest innerNode typeDoc <- docSharedWrapper layoutType typ layoutLhsAndType hasComments lhs "=" typeDoc #if MIN_VERSION_ghc(8,8,0) layoutHsTyPats :: [HsArg (LHsType GhcPs) (LHsKind GhcPs)] -> [ToBriDocM BriDocNumbered] layoutHsTyPats pats = pats <&> \case HsValArg tm -> layoutType tm HsTypeArg _l ty -> docSeq [docLit $ Text.pack "@", layoutType ty] -- we ignore the SourceLoc here.. this LPat not being (L _ Pat{}) change -- is a bit strange. Hopefully this does not ignore any important -- annotations. HsArgPar _l -> error "brittany internal error: HsArgPar{}" #else layoutHsTyPats :: [LHsType GhcPs] -> [ToBriDocM BriDocNumbered] layoutHsTyPats pats = layoutType <$> pats #endif -------------------------------------------------------------------------------- -- ClsInstDecl -------------------------------------------------------------------------------- -- | Layout an @instance@ declaration -- -- Layout signatures and bindings using the corresponding layouters from the -- top-level. Layout the instance head, type family instances, and data family -- instances using ExactPrint. layoutClsInst :: ToBriDoc ClsInstDecl layoutClsInst lcid@(L _ cid) = docLines [ layoutInstanceHead , docEnsureIndent BrIndentRegular $ docSetIndentLevel $ docSortedLines $ fmap layoutAndLocateSig (cid_sigs cid) ++ fmap layoutAndLocateBind (bagToList $ cid_binds cid) ++ fmap layoutAndLocateTyFamInsts (cid_tyfam_insts cid) ++ fmap layoutAndLocateDataFamInsts (cid_datafam_insts cid) ] where layoutInstanceHead :: ToBriDocM BriDocNumbered #if MIN_VERSION_ghc(8,10,1) /* ghc-8.10.1 */ layoutInstanceHead = briDocByExactNoComment $ InstD NoExtField . ClsInstD NoExtField . removeChildren <$> lcid #else layoutInstanceHead = briDocByExactNoComment $ InstD NoExt . ClsInstD NoExt . removeChildren <$> lcid #endif removeChildren :: ClsInstDecl p -> ClsInstDecl p removeChildren c = c { cid_binds = emptyBag , cid_sigs = [] , cid_tyfam_insts = [] , cid_datafam_insts = [] } -- | Like 'docLines', but sorts the lines based on location docSortedLines :: [ToBriDocM (Located BriDocNumbered)] -> ToBriDocM BriDocNumbered docSortedLines l = allocateNode . BDFLines . fmap unLoc . List.sortOn getLoc =<< sequence l layoutAndLocateSig :: ToBriDocC (Sig GhcPs) (Located BriDocNumbered) layoutAndLocateSig lsig@(L loc _) = L loc <$> layoutSig lsig layoutAndLocateBind :: ToBriDocC (HsBind GhcPs) (Located BriDocNumbered) layoutAndLocateBind lbind@(L loc _) = L loc <$> (joinBinds =<< layoutBind lbind) joinBinds :: Either [BriDocNumbered] BriDocNumbered -> ToBriDocM BriDocNumbered joinBinds = \case Left ns -> docLines $ return <$> ns Right n -> return n layoutAndLocateTyFamInsts :: ToBriDocC (TyFamInstDecl GhcPs) (Located BriDocNumbered) layoutAndLocateTyFamInsts ltfid@(L loc tfid) = L loc <$> layoutTyFamInstDecl True ltfid tfid layoutAndLocateDataFamInsts :: ToBriDocC (DataFamInstDecl GhcPs) (Located BriDocNumbered) layoutAndLocateDataFamInsts ldfid@(L loc _) = L loc <$> layoutDataFamInstDecl ldfid -- | Send to ExactPrint then remove unecessary whitespace layoutDataFamInstDecl :: ToBriDoc DataFamInstDecl layoutDataFamInstDecl ldfid = fmap stripWhitespace <$> briDocByExactNoComment ldfid -- | ExactPrint adds indentation/newlines to @data@/@type@ declarations stripWhitespace :: BriDocF f -> BriDocF f stripWhitespace (BDFExternal ann anns b t) = BDFExternal ann anns b $ stripWhitespace' t stripWhitespace b = b -- | This fixes two issues of output coming from Exactprinting -- associated (data) type decls. Firstly we place the output into docLines, -- so one newline coming from Exactprint is superfluous, so we drop the -- first (empty) line. The second issue is Exactprint indents the first -- member in a strange fashion: -- -- input: -- -- > instance MyClass Int where -- > -- | This data is very important -- > data MyData = IntData -- > { intData :: String -- > , intData2 :: Int -- > } -- -- output of just exactprinting the associated data type syntax node -- -- > -- > -- | This data is very important -- > data MyData = IntData -- > { intData :: String -- > , intData2 :: Int -- > } -- -- To fix this, we strip whitespace from the start of the comments and the -- first line of the declaration, stopping when we see "data" or "type" at -- the start of a line. I.e., this function yields -- -- > -- | This data is very important -- > data MyData = IntData -- > { intData :: String -- > , intData2 :: Int -- > } -- -- Downside apart from being a hacky and brittle fix is that this removes -- possible additional indentation from comments before the first member. -- -- But the whole thing is just a temporary measure until brittany learns -- to layout data/type decls. stripWhitespace' :: Text -> Text stripWhitespace' t = Text.intercalate (Text.pack "\n") $ go $ List.drop 1 $ Text.lines t where go [] = [] go (line1 : lineR) = case Text.stripStart line1 of st | isTypeOrData st -> st : lineR | otherwise -> st : go lineR isTypeOrData t' = (Text.pack "type" `Text.isPrefixOf` t') || (Text.pack "newtype" `Text.isPrefixOf` t') || (Text.pack "data" `Text.isPrefixOf` t') -------------------------------------------------------------------------------- -- Common Helpers -------------------------------------------------------------------------------- layoutLhsAndType :: Bool -> ToBriDocM BriDocNumbered -> String -> ToBriDocM BriDocNumbered -> ToBriDocM BriDocNumbered layoutLhsAndType hasComments lhs sep typeDoc = do runFilteredAlternative $ do -- (separators probably are "=" or "::") -- lhs = type -- lhs :: type addAlternativeCond (not hasComments) $ docSeq [lhs, docSeparator, docLitS sep, docSeparator, docForceSingleline typeDoc] -- lhs -- :: typeA -- -> typeB -- lhs -- = typeA -- -> typeB addAlternative $ docAddBaseY BrIndentRegular $ docPar lhs $ docCols ColTyOpPrefix [ appSep $ docLitS sep , docAddBaseY (BrIndentSpecial (length sep + 1)) typeDoc ]