{-# LANGUAGE LambdaCase #-} {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} module Ormolu.Printer.Meat.Declaration.Value ( p_valDecl, p_pat, p_hsExpr, p_hsSplice, p_stringLit, ) where import Control.Monad import Data.Bool (bool) import Data.Char (isPunctuation, isSymbol) import Data.Data hiding (Infix, Prefix) import Data.Function (on) import Data.Functor ((<&>)) import Data.Generics.Schemes (everything) import Data.List (intersperse, sortBy) import Data.List.NonEmpty (NonEmpty (..), (<|)) import qualified Data.List.NonEmpty as NE import Data.Maybe (isJust) import Data.Text (Text) import qualified Data.Text as Text import GHC.Data.Bag (bagToList) import GHC.Hs.Binds import GHC.Hs.Expr import GHC.Hs.Extension import GHC.Hs.Lit import GHC.Hs.Pat import GHC.Hs.Type import GHC.LanguageExtensions.Type (Extension (NegativeLiterals)) import GHC.Parser.Annotation import GHC.Parser.CharClass (is_space) import GHC.Types.Basic import GHC.Types.Name.Occurrence (occNameString) import GHC.Types.Name.Reader import GHC.Types.SrcLoc import Ormolu.Config import Ormolu.Printer.Combinators import Ormolu.Printer.Internal import Ormolu.Printer.Meat.Common import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration import Ormolu.Printer.Meat.Declaration.Signature import Ormolu.Printer.Meat.Type import Ormolu.Printer.Operators import Ormolu.Utils -- | Style of a group of equations. data MatchGroupStyle = Function (Located RdrName) | PatternBind | Case | Lambda | LambdaCase -- | Style of equations in a group. data GroupStyle = EqualSign | RightArrow -- | Expression placement. This marks the places where expressions that -- implement handing forms may use them. data Placement = -- | Multi-line layout should cause -- insertion of a newline and indentation -- bump Normal | -- | Expressions that have hanging form -- should use it and avoid bumping one level -- of indentation Hanging deriving (Eq, Show) p_valDecl :: HsBindLR GhcPs GhcPs -> R () p_valDecl = \case FunBind NoExtField funId funMatches _ -> p_funBind funId funMatches PatBind NoExtField pat grhss _ -> p_match PatternBind False NoSrcStrict [pat] grhss VarBind {} -> notImplemented "VarBinds" -- introduced by the type checker AbsBinds {} -> notImplemented "AbsBinds" -- introduced by the type checker PatSynBind NoExtField psb -> p_patSynBind psb p_funBind :: Located RdrName -> MatchGroup GhcPs (LHsExpr GhcPs) -> R () p_funBind name = p_matchGroup (Function name) p_matchGroup :: MatchGroupStyle -> MatchGroup GhcPs (LHsExpr GhcPs) -> R () p_matchGroup = p_matchGroup' exprPlacement p_hsExpr p_matchGroup' :: Data body => -- | How to get body placement (body -> Placement) -> -- | How to print body (body -> R ()) -> -- | Style of this group of equations MatchGroupStyle -> -- | Match group MatchGroup GhcPs (Located body) -> R () p_matchGroup' placer render style mg@MG {..} = do let ob = case style of Case -> bracesIfEmpty LambdaCase -> bracesIfEmpty _ -> dontUseBraces where bracesIfEmpty = if isEmptyMatchGroup mg then useBraces else id -- Since we are forcing braces on 'sepSemi' based on 'ob', we have to -- restore the brace state inside the sepsemi. ub <- bool dontUseBraces useBraces <$> canUseBraces ob $ sepSemi (located' (ub . p_Match)) (unLoc mg_alts) where p_Match m@Match {..} = p_match' placer render (adjustMatchGroupStyle m style) (isInfixMatch m) (matchStrictness m) m_pats m_grhss -- | Function id obtained through pattern matching on 'FunBind' should not -- be used to print the actual equations because the different ‘RdrNames’ -- used in the equations may have different “decorations” (such as backticks -- and paretheses) associated with them. It is necessary to use per-equation -- names obtained from 'm_ctxt' of 'Match'. This function replaces function -- name inside of 'Function' accordingly. adjustMatchGroupStyle :: Match GhcPs body -> MatchGroupStyle -> MatchGroupStyle adjustMatchGroupStyle m = \case Function _ -> (Function . mc_fun . m_ctxt) m style -> style matchStrictness :: Match id body -> SrcStrictness matchStrictness match = case m_ctxt match of FunRhs {mc_strictness = s} -> s _ -> NoSrcStrict p_match :: -- | Style of the group MatchGroupStyle -> -- | Is this an infix match? Bool -> -- | Strictness prefix (FunBind) SrcStrictness -> -- | Argument patterns [LPat GhcPs] -> -- | Equations GRHSs GhcPs (LHsExpr GhcPs) -> R () p_match = p_match' exprPlacement p_hsExpr p_match' :: Data body => -- | How to get body placement (body -> Placement) -> -- | How to print body (body -> R ()) -> -- | Style of this group of equations MatchGroupStyle -> -- | Is this an infix match? Bool -> -- | Strictness prefix (FunBind) SrcStrictness -> -- | Argument patterns [LPat GhcPs] -> -- | Equations GRHSs GhcPs (Located body) -> R () p_match' placer render style isInfix strictness m_pats GRHSs {..} = do -- Normally, since patterns may be placed in a multi-line layout, it is -- necessary to bump indentation for the pattern group so it's more -- indented than function name. This in turn means that indentation for -- the body should also be bumped. Normally this would mean that bodies -- would start with two indentation steps applied, which is ugly, so we -- need to be a bit more clever here and bump indentation level only when -- pattern group is multiline. case strictness of NoSrcStrict -> return () SrcStrict -> txt "!" SrcLazy -> txt "~" indentBody <- case NE.nonEmpty m_pats of Nothing -> False <$ case style of Function name -> p_rdrName name _ -> return () Just ne_pats -> do let combinedSpans = case style of Function name -> combineSrcSpans (getLoc name) patSpans _ -> patSpans patSpans = combineSrcSpans' (getLoc <$> ne_pats) indentBody = not (isOneLineSpan combinedSpans) switchLayout [combinedSpans] $ do let stdCase = sep breakpoint (located' p_pat) m_pats case style of Function name -> p_infixDefHelper isInfix indentBody (p_rdrName name) (located' p_pat <$> m_pats) PatternBind -> stdCase Case -> stdCase Lambda -> do let needsSpace = case unLoc (NE.head ne_pats) of LazyPat _ _ -> True BangPat _ _ -> True SplicePat _ _ -> True _ -> False txt "\\" when needsSpace space sitcc stdCase LambdaCase -> stdCase return indentBody let -- Calculate position of end of patterns. This is useful when we decide -- about putting certain constructions in hanging positions. endOfPats = case NE.nonEmpty m_pats of Nothing -> case style of Function name -> Just (getLoc name) _ -> Nothing Just pats -> (Just . getLoc . NE.last) pats isCase = \case Case -> True LambdaCase -> True _ -> False hasGuards = withGuards grhssGRHSs grhssSpan = combineSrcSpans' $ getGRHSSpan . unLoc <$> NE.fromList grhssGRHSs patGrhssSpan = maybe grhssSpan (combineSrcSpans grhssSpan . srcLocSpan . srcSpanEnd) endOfPats placement = case endOfPats of Nothing -> blockPlacement placer grhssGRHSs Just spn -> if onTheSameLine spn grhssSpan then blockPlacement placer grhssGRHSs else Normal p_body = do let groupStyle = if isCase style && hasGuards then RightArrow else EqualSign sep newline (located' (p_grhs' placer render groupStyle)) grhssGRHSs p_where = do let whereIsEmpty = eqEmptyLocalBinds (unLoc grhssLocalBinds) unless (eqEmptyLocalBinds (unLoc grhssLocalBinds)) $ do breakpoint indentWhere <- getPrinterOpt poIndentWheres bool (inciByFrac $ -2) id indentWhere $ txt "where" unless whereIsEmpty breakpoint inciIf indentWhere $ located grhssLocalBinds p_hsLocalBinds inciIf indentBody $ do unless (length grhssGRHSs > 1) $ case style of Function _ | hasGuards -> return () Function _ -> space >> inci equals PatternBind -> space >> inci equals s | isCase s && hasGuards -> return () _ -> space >> txt "->" switchLayout [patGrhssSpan] $ placeHanging placement p_body inci p_where p_grhs :: GroupStyle -> GRHS GhcPs (LHsExpr GhcPs) -> R () p_grhs = p_grhs' exprPlacement p_hsExpr p_grhs' :: Data body => -- | How to get body placement (body -> Placement) -> -- | How to print body (body -> R ()) -> GroupStyle -> GRHS GhcPs (Located body) -> R () p_grhs' placer render style (GRHS NoExtField guards body) = case guards of [] -> p_body xs -> do txt "|" space sitcc (sep commaDel (sitcc . located' p_stmt) xs) space inci $ case style of EqualSign -> equals RightArrow -> txt "->" placeHanging placement p_body where placement = case endOfGuards of Nothing -> placer (unLoc body) Just spn -> if onTheSameLine spn (getLoc body) then placer (unLoc body) else Normal endOfGuards = case NE.nonEmpty guards of Nothing -> Nothing Just gs -> (Just . getLoc . NE.last) gs p_body = located body render p_hsCmd :: HsCmd GhcPs -> R () p_hsCmd = p_hsCmd' N p_hsCmd' :: BracketStyle -> HsCmd GhcPs -> R () p_hsCmd' s = \case HsCmdArrApp NoExtField body input arrType rightToLeft -> do let (l, r) = if rightToLeft then (body, input) else (input, body) located l p_hsExpr breakpoint inci $ do case (arrType, rightToLeft) of (HsFirstOrderApp, True) -> txt "-<" (HsHigherOrderApp, True) -> txt "-<<" (HsFirstOrderApp, False) -> txt ">-" (HsHigherOrderApp, False) -> txt ">>-" placeHanging (exprPlacement (unLoc input)) $ located r p_hsExpr HsCmdArrForm NoExtField form Prefix _ cmds -> banana s $ do located form p_hsExpr unless (null cmds) $ do breakpoint inci (sequence_ (intersperse breakpoint (located' p_hsCmdTop <$> cmds))) HsCmdArrForm NoExtField form Infix _ [left, right] -> let opTree = OpBranch (cmdOpTree left) form (cmdOpTree right) in p_cmdOpTree (reassociateOpTree getOpName opTree) HsCmdArrForm NoExtField _ Infix _ _ -> notImplemented "HsCmdArrForm" HsCmdApp NoExtField cmd expr -> do located cmd (p_hsCmd' s) space located expr p_hsExpr HsCmdLam NoExtField mgroup -> p_matchGroup' cmdPlacement p_hsCmd Lambda mgroup HsCmdPar NoExtField c -> parens N $ sitcc $ located c p_hsCmd HsCmdCase NoExtField e mgroup -> p_case cmdPlacement p_hsCmd e mgroup HsCmdLamCase NoExtField mgroup -> p_lamcase cmdPlacement p_hsCmd mgroup HsCmdIf NoExtField _ if' then' else' -> p_if cmdPlacement p_hsCmd if' then' else' HsCmdLet NoExtField localBinds c -> p_let p_hsCmd localBinds c HsCmdDo NoExtField es -> do txt "do" p_stmts cmdPlacement (p_hsCmd' S) es p_hsCmdTop :: HsCmdTop GhcPs -> R () p_hsCmdTop (HsCmdTop NoExtField cmd) = located cmd p_hsCmd -- | Render an expression preserving blank lines between such consecutive -- expressions found in the original source code. withSpacing :: -- | Rendering function (a -> R ()) -> -- | Entity to render Located a -> R () withSpacing f l = located l $ \x -> do case getLoc l of UnhelpfulSpan _ -> f x RealSrcSpan currentSpn _ -> do getSpanMark >>= \case -- Spacing before comments will be handled by the code -- that prints comments, so we just have to deal with -- blank lines between statements here. Just (StatementSpan lastSpn) -> if srcSpanStartLine currentSpn > srcSpanEndLine lastSpn + 1 then newline else return () _ -> return () f x -- In some cases the (f x) expression may insert a new mark. We want -- to be careful not to override comment marks. getSpanMark >>= \case Just (HaddockSpan _ _) -> return () Just (CommentSpan _) -> return () _ -> setSpanMark (StatementSpan currentSpn) p_stmt :: Stmt GhcPs (LHsExpr GhcPs) -> R () p_stmt = p_stmt' exprPlacement p_hsExpr p_stmt' :: Data body => -- | Placer (body -> Placement) -> -- | Render (body -> R ()) -> -- | Statement to render Stmt GhcPs (Located body) -> R () p_stmt' placer render = \case LastStmt NoExtField body _ _ -> located body render BindStmt NoExtField p f@(L l x) -> do located p p_pat space txt "<-" let loc = getLoc p placement | isOneLineSpan (mkSrcSpan (srcSpanEnd loc) (srcSpanStart l)) = placer x | otherwise = Normal switchLayout [loc, l] $ placeHanging placement (located f render) ApplicativeStmt {} -> notImplemented "ApplicativeStmt" -- generated by renamer BodyStmt NoExtField body _ _ -> located body render LetStmt NoExtField binds -> do txt "let" space sitcc $ located binds p_hsLocalBinds ParStmt {} -> -- 'ParStmt' should always be eliminated in 'gatherStmt' already, such -- that it never occurs in 'p_stmt''. Consequently, handling it here -- would be redundant. notImplemented "ParStmt" TransStmt {..} -> -- 'TransStmt' only needs to account for render printing itself, since -- pretty printing of relevant statements (e.g., in 'trS_stmts') is -- handled through 'gatherStmt'. case (trS_form, trS_by) of (ThenForm, Nothing) -> do txt "then" breakpoint inci $ located trS_using p_hsExpr (ThenForm, Just e) -> do txt "then" breakpoint inci $ located trS_using p_hsExpr breakpoint txt "by" breakpoint inci $ located e p_hsExpr (GroupForm, Nothing) -> do txt "then group using" breakpoint inci $ located trS_using p_hsExpr (GroupForm, Just e) -> do txt "then group by" breakpoint inci $ located e p_hsExpr breakpoint txt "using" breakpoint inci $ located trS_using p_hsExpr RecStmt {..} -> do txt "rec" space sitcc $ sepSemi (withSpacing (p_stmt' placer render)) recS_stmts p_stmts :: Data body => -- | Placer (body -> Placement) -> -- | Render (body -> R ()) -> -- | Statements to render Located [Located (Stmt GhcPs (Located body))] -> R () p_stmts placer render es = do breakpoint ub <- layoutToBraces <$> getLayout inci . located es $ sepSemi (ub . withSpacing (p_stmt' placer render)) gatherStmt :: ExprLStmt GhcPs -> [[ExprLStmt GhcPs]] gatherStmt (L _ (ParStmt NoExtField block _ _)) = foldr ((<>) . gatherStmtBlock) [] block gatherStmt (L s stmt@TransStmt {..}) = foldr liftAppend [] ((gatherStmt <$> trS_stmts) <> pure [[L s stmt]]) gatherStmt stmt = [[stmt]] gatherStmtBlock :: ParStmtBlock GhcPs GhcPs -> [[ExprLStmt GhcPs]] gatherStmtBlock (ParStmtBlock _ stmts _ _) = foldr (liftAppend . gatherStmt) [] stmts p_hsLocalBinds :: HsLocalBindsLR GhcPs GhcPs -> R () p_hsLocalBinds = \case HsValBinds NoExtField (ValBinds NoExtField bag lsigs) -> do -- When in a single-line layout, there is a chance that the inner -- elements will also contain semicolons and they will confuse the -- parser. so we request braces around every element except the last. br <- layoutToBraces <$> getLayout let items = let injectLeft (L l x) = L l (Left x) injectRight (L l x) = L l (Right x) in (injectLeft <$> bagToList bag) ++ (injectRight <$> lsigs) positionToBracing = \case SinglePos -> id FirstPos -> br MiddlePos -> br LastPos -> id p_item' (p, item) = positionToBracing p $ withSpacing (either p_valDecl p_sigDecl) item binds = sortBy (leftmost_smallest `on` getLoc) items sitcc $ sepSemi p_item' (attachRelativePos binds) HsValBinds NoExtField _ -> notImplemented "HsValBinds" HsIPBinds NoExtField (IPBinds NoExtField xs) -> -- Second argument of IPBind is always Left before type-checking. let p_ipBind (IPBind NoExtField (Left name) expr) = do atom name space equals breakpoint useBraces $ inci $ located expr p_hsExpr p_ipBind (IPBind NoExtField (Right _) _) = -- Should only occur after the type checker notImplemented "IPBind _ (Right _) _" in sepSemi (located' p_ipBind) xs EmptyLocalBinds NoExtField -> return () p_hsRecField :: HsRecField' RdrName (LHsExpr GhcPs) -> R () p_hsRecField HsRecField {..} = do p_rdrName hsRecFieldLbl unless hsRecPun $ do space equals let placement = if onTheSameLine (getLoc hsRecFieldLbl) (getLoc hsRecFieldArg) then exprPlacement (unLoc hsRecFieldArg) else Normal placeHanging placement (located hsRecFieldArg p_hsExpr) p_hsExpr :: HsExpr GhcPs -> R () p_hsExpr = p_hsExpr' N p_hsExpr' :: BracketStyle -> HsExpr GhcPs -> R () p_hsExpr' s = \case HsVar NoExtField name -> p_rdrName name HsUnboundVar NoExtField occ -> atom occ HsConLikeOut NoExtField _ -> notImplemented "HsConLikeOut" HsRecFld NoExtField x -> case x of Unambiguous NoExtField name -> p_rdrName name Ambiguous NoExtField name -> p_rdrName name HsOverLabel NoExtField _ v -> do txt "#" atom v HsIPVar NoExtField (HsIPName name) -> do txt "?" atom name HsOverLit NoExtField v -> atom (ol_val v) HsLit NoExtField lit -> case lit of HsString (SourceText stxt) _ -> p_stringLit stxt HsStringPrim (SourceText stxt) _ -> p_stringLit stxt r -> atom r HsLam NoExtField mgroup -> p_matchGroup Lambda mgroup HsLamCase NoExtField mgroup -> p_lamcase exprPlacement p_hsExpr mgroup HsApp NoExtField f x -> do let -- In order to format function applications with multiple parameters -- nicer, traverse the AST to gather the function and all the -- parameters together. gatherArgs f' knownArgs = case f' of L _ (HsApp _ l r) -> gatherArgs l (r <| knownArgs) _ -> (f', knownArgs) (func, args) = gatherArgs f (x :| []) -- We need to handle the last argument specially if it is a -- hanging construct, so separate it from the rest. (initp, lastp) = (NE.init args, NE.last args) initSpan = combineSrcSpans' $ getLoc f :| [(srcLocSpan . srcSpanStart . getLoc) lastp] -- Hang the last argument only if the initial arguments span one -- line. placement = if isOneLineSpan initSpan then exprPlacement (unLoc lastp) else Normal -- If the last argument is not hanging, just separate every argument as -- usual. If it is hanging, print the initial arguments and hang the -- last one. Also, use braces around the every argument except the last -- one. case placement of Normal -> do let -- Usually we want to bump indentation for arguments for the -- sake of readability. However: -- When the function is itself a multi line do-block or a case -- expression, we can't indent by indentStep or more. -- When we are on the other hand *in* a do block, we have to -- indent by at least 1. -- Thus, we indent by half of indentStep when the function is -- a multi line do block or case expression. indentArg | isOneLineSpan (getLoc func) = case unLoc func of HsDo {} -> inciBy 2 _ -> inci | otherwise = case unLoc func of HsDo {} -> inciHalf HsCase {} -> inciHalf HsLamCase {} -> inciHalf _ -> inci ub <- getLayout <&> \case SingleLine -> useBraces MultiLine -> id ub $ do located func (p_hsExpr' s) breakpoint indentArg $ sep breakpoint (located' p_hsExpr) initp indentArg $ do unless (null initp) breakpoint located lastp p_hsExpr Hanging -> do useBraces . switchLayout [initSpan] $ do located func (p_hsExpr' s) breakpoint sep breakpoint (located' p_hsExpr) initp placeHanging placement . dontUseBraces $ located lastp p_hsExpr HsAppType NoExtField e a -> do located e p_hsExpr breakpoint inci $ do txt "@" -- Insert a space when the type is represented as a TH splice to avoid -- gluing @ and $ together. case unLoc (hswc_body a) of HsSpliceTy {} -> space _ -> return () located (hswc_body a) p_hsType OpApp NoExtField x op y -> do let opTree = OpBranch (exprOpTree x) op (exprOpTree y) p_exprOpTree s (reassociateOpTree getOpName opTree) NegApp NoExtField e NoExtField -> do negativeLiterals <- isExtensionEnabled NegativeLiterals let isLiteral = case unLoc e of HsLit {} -> True HsOverLit {} -> True _ -> False txt "-" -- If NegativeLiterals is enabled, we have to insert a space before -- negated literals, as `- 1` and `-1` have differing AST. when (negativeLiterals && isLiteral) space located e p_hsExpr HsPar NoExtField e -> do parens s $ sitcc $ located e $ dontUseBraces . p_hsExpr SectionL NoExtField x op -> do located x p_hsExpr breakpoint inci (located op p_hsExpr) SectionR NoExtField op x -> do located op p_hsExpr useRecordDot' <- useRecordDot let isRecordDot' = isRecordDot (unLoc op) (getLoc x) unless (useRecordDot' && isRecordDot') breakpoint inci (located x p_hsExpr) ExplicitTuple NoExtField args boxity -> let isSection = any (isMissing . unLoc) args isMissing = \case Missing NoExtField -> True _ -> False p_arg = \case Present NoExtField x -> located x p_hsExprListItem Missing NoExtField -> pure () p_larg = sitcc . located' p_arg parens' = case boxity of Boxed -> parens Unboxed -> parensHash in if isSection then switchLayout [] . parens' s $ sep comma p_larg args else switchLayout (getLoc <$> args) . parens' s $ sep commaDel p_larg args ExplicitSum NoExtField tag arity e -> p_unboxedSum N tag arity (located e p_hsExpr) HsCase NoExtField e mgroup -> p_case exprPlacement p_hsExpr e mgroup HsIf NoExtField if' then' else' -> p_if exprPlacement p_hsExpr if' then' else' HsMultiIf NoExtField guards -> do txt "if" breakpoint inci . inci $ sep newline (located' (p_grhs RightArrow)) guards HsLet NoExtField localBinds e -> p_let p_hsExpr localBinds e HsDo NoExtField ctx es -> do let doBody moduleName header = do forM_ moduleName $ \m -> atom m *> txt "." txt header p_stmts exprPlacement (p_hsExpr' S) es compBody = brackets N . located es $ \xs -> do let p_parBody = sep (breakpoint >> txt "|" >> space) p_seqBody p_seqBody = sitcc' . sep commaDel (located' (sitcc . p_stmt)) sitcc' x = getPrinterOpt poCommaStyle >>= \case Leading -> id x Trailing -> sitcc x stmts = init xs yield = last xs lists = foldr (liftAppend . gatherStmt) [] stmts located yield p_stmt breakpoint txt "|" space p_parBody lists case ctx of DoExpr moduleName -> doBody moduleName "do" MDoExpr moduleName -> doBody moduleName "mdo" ListComp -> compBody MonadComp -> compBody ArrowExpr -> notImplemented "ArrowExpr" GhciStmtCtxt -> notImplemented "GhciStmtCtxt" PatGuard _ -> notImplemented "PatGuard" ParStmtCtxt _ -> notImplemented "ParStmtCtxt" TransStmtCtxt _ -> notImplemented "TransStmtCtxt" ExplicitList _ _ xs -> brackets s $ sep commaDel (sitcc . located' p_hsExprListItem) xs RecordCon {..} -> do located rcon_con_name atom breakpointPreRecordBrace let HsRecFields {..} = rcon_flds updName f = (f :: HsRecField GhcPs (LHsExpr GhcPs)) { hsRecFieldLbl = case unLoc $ hsRecFieldLbl f of FieldOcc _ n -> n } fields = located' (p_hsRecField . updName) <$> rec_flds dotdot = case rec_dotdot of Just {} -> [txt ".."] Nothing -> [] inci . braces N $ sep commaDel sitcc (fields <> dotdot) RecordUpd {..} -> do located rupd_expr p_hsExpr useRecordDot' <- useRecordDot let mrs sp = case getLoc sp of RealSrcSpan r _ -> Just r _ -> Nothing let isPluginForm = ((1 +) . srcSpanEndCol <$> mrs rupd_expr) == (srcSpanStartCol <$> mrs (head rupd_flds)) && onTheSameLine (getLoc rupd_expr) (getLoc $ head rupd_flds) unless (useRecordDot' && isPluginForm) breakpointPreRecordBrace let updName f = (f :: HsRecUpdField GhcPs) { hsRecFieldLbl = case unLoc $ hsRecFieldLbl f of Ambiguous _ n -> n Unambiguous _ n -> n } updBraces = if useRecordDot' && isPluginForm then recordDotBraces else inci . braces N updBraces $ sep commaDel (sitcc . located' (p_hsRecField . updName)) rupd_flds ExprWithTySig NoExtField x HsWC {hswc_body = HsIB {..}} -> sitcc $ do located x p_hsExpr space txt "::" breakpoint inci $ located hsib_body p_hsType ArithSeq NoExtField _ x -> case x of From from -> brackets s $ do located from p_hsExpr breakpoint txt ".." FromThen from next -> brackets s $ do sep commaDel (located' p_hsExpr) [from, next] breakpoint txt ".." FromTo from to -> brackets s $ do located from p_hsExpr breakpoint txt ".." space located to p_hsExpr FromThenTo from next to -> brackets s $ do sep commaDel (located' p_hsExpr) [from, next] breakpoint txt ".." space located to p_hsExpr HsBracket NoExtField x -> p_hsBracket x HsRnBracketOut {} -> notImplemented "HsRnBracketOut" HsTcBracketOut {} -> notImplemented "HsTcBracketOut" HsSpliceE NoExtField splice -> p_hsSplice splice HsProc NoExtField p e -> do txt "proc" located p $ \x -> do breakpoint inci (p_pat x) breakpoint txt "->" placeHanging (cmdTopPlacement (unLoc e)) $ located e p_hsCmdTop HsStatic _ e -> do txt "static" breakpoint inci (located e p_hsExpr) HsTick {} -> notImplemented "HsTick" HsBinTick {} -> notImplemented "HsBinTick" HsPragE NoExtField prag x -> case prag of HsPragSCC NoExtField _ name -> do txt "{-# SCC " atom name txt " #-}" breakpoint located x p_hsExpr HsPragTick {} -> notImplemented "HsTickPragma" p_patSynBind :: PatSynBind GhcPs GhcPs -> R () p_patSynBind PSB {..} = do let rhs = do space case psb_dir of Unidirectional -> do txt "<-" breakpoint located psb_def p_pat ImplicitBidirectional -> do equals breakpoint located psb_def p_pat ExplicitBidirectional mgroup -> do txt "<-" breakpoint located psb_def p_pat breakpoint txt "where" breakpoint inci (p_matchGroup (Function psb_id) mgroup) txt "pattern" case psb_args of PrefixCon xs -> do space p_rdrName psb_id inci $ do switchLayout (getLoc <$> xs) $ do unless (null xs) breakpoint sitcc (sep breakpoint p_rdrName xs) rhs RecCon xs -> do space p_rdrName psb_id inci $ do switchLayout (getLoc . recordPatSynPatVar <$> xs) $ do unless (null xs) breakpointPreRecordBrace braces N $ sep commaDel (p_rdrName . recordPatSynPatVar) xs rhs InfixCon l r -> do switchLayout [getLoc l, getLoc r] $ do space p_rdrName l breakpoint inci $ do p_rdrName psb_id space p_rdrName r inci rhs p_case :: Data body => -- | Placer (body -> Placement) -> -- | Render (body -> R ()) -> -- | Expression LHsExpr GhcPs -> -- | Match group MatchGroup GhcPs (Located body) -> R () p_case placer render e mgroup = do txt "case" space located e p_hsExpr space txt "of" breakpoint inci (p_matchGroup' placer render Case mgroup) p_lamcase :: Data body => -- | Placer (body -> Placement) -> -- | Render (body -> R ()) -> -- | Expression MatchGroup GhcPs (Located body) -> R () p_lamcase placer render mgroup = do txt "\\case" breakpoint inci (p_matchGroup' placer render LambdaCase mgroup) p_if :: Data body => -- | Placer (body -> Placement) -> -- | Render (body -> R ()) -> -- | If LHsExpr GhcPs -> -- | Then Located body -> -- | Else Located body -> R () p_if placer render if' then' else' = do txt "if" space located if' p_hsExpr breakpoint inci $ do txt "then" space located then' $ \x -> placeHanging (placer x) (render x) breakpoint txt "else" space located else' $ \x -> placeHanging (placer x) (render x) p_let :: Data body => -- | Render (body -> R ()) -> Located (HsLocalBindsLR GhcPs GhcPs) -> Located body -> R () p_let render localBinds e = sitcc $ do txt "let" space dontUseBraces $ sitcc (located localBinds p_hsLocalBinds) vlayout space (newline >> txt " ") txt "in" space sitcc (located e render) p_pat :: Pat GhcPs -> R () p_pat = \case WildPat NoExtField -> txt "_" VarPat NoExtField name -> p_rdrName name LazyPat NoExtField pat -> do txt "~" located pat p_pat AsPat NoExtField name pat -> do p_rdrName name txt "@" located pat p_pat ParPat NoExtField pat -> located pat (parens S . sitcc . p_pat) BangPat NoExtField pat -> do txt "!" located pat p_pat ListPat NoExtField pats -> brackets S $ sep commaDel (located' p_pat) pats TuplePat NoExtField pats boxing -> do let parens' = case boxing of Boxed -> parens S Unboxed -> parensHash S parens' $ sep commaDel (sitcc . located' p_pat) pats SumPat NoExtField pat tag arity -> p_unboxedSum S tag arity (located pat p_pat) ConPat NoExtField pat details -> case details of PrefixCon xs -> sitcc $ do p_rdrName pat unless (null xs) $ do breakpoint inci . sitcc $ sep breakpoint (sitcc . located' p_pat) xs RecCon (HsRecFields fields dotdot) -> do p_rdrName pat breakpointPreRecordBrace let f = \case Nothing -> txt ".." Just x -> located x p_pat_hsRecField inci . braces N . sep commaDel f $ case dotdot of Nothing -> Just <$> fields Just (L _ n) -> (Just <$> take n fields) ++ [Nothing] InfixCon l r -> do switchLayout [getLoc l, getLoc r] $ do located l p_pat breakpoint inci $ do p_rdrName pat space located r p_pat ViewPat NoExtField expr pat -> sitcc $ do located expr p_hsExpr space txt "->" breakpoint inci (located pat p_pat) SplicePat NoExtField splice -> p_hsSplice splice LitPat NoExtField p -> atom p NPat NoExtField v (isJust -> isNegated) NoExtField -> do when isNegated $ do txt "-" negativeLiterals <- isExtensionEnabled NegativeLiterals when negativeLiterals space located v (atom . ol_val) NPlusKPat NoExtField n k _ _ _ -> sitcc $ do p_rdrName n breakpoint inci $ do txt "+" space located k (atom . ol_val) SigPat NoExtField pat HsPS {..} -> do located pat p_pat p_typeAscription (HsWC NoExtField (HsIB NoExtField hsps_body)) p_pat_hsRecField :: HsRecField' (FieldOcc GhcPs) (LPat GhcPs) -> R () p_pat_hsRecField HsRecField {..} = do located hsRecFieldLbl $ \x -> p_rdrName (rdrNameFieldOcc x) unless hsRecPun $ do space equals breakpoint inci (located hsRecFieldArg p_pat) p_unboxedSum :: BracketStyle -> ConTag -> Arity -> R () -> R () p_unboxedSum s tag arity m = do let before = tag - 1 after = arity - before - 1 args = replicate before Nothing <> [Just m] <> replicate after Nothing f x = case x :: Maybe (R ()) of Nothing -> space Just m' -> do space m' space parensHash s $ sep (txt "|") f args p_hsSplice :: HsSplice GhcPs -> R () p_hsSplice = \case HsTypedSplice NoExtField deco _ expr -> p_hsSpliceTH True expr deco HsUntypedSplice NoExtField deco _ expr -> p_hsSpliceTH False expr deco HsQuasiQuote NoExtField _ quoterName srcSpan str -> do txt "[" p_rdrName (L srcSpan quoterName) txt "|" -- QuasiQuoters often rely on precise custom strings. We cannot do any -- formatting here without potentially breaking someone's code. atom str txt "|]" HsSpliced {} -> notImplemented "HsSpliced" p_hsSpliceTH :: -- | Typed splice? Bool -> -- | Splice expression LHsExpr GhcPs -> -- | Splice decoration SpliceDecoration -> R () p_hsSpliceTH isTyped expr = \case DollarSplice -> do txt decoSymbol located expr (sitcc . p_hsExpr) BareSplice -> located expr (sitcc . p_hsExpr) where decoSymbol = if isTyped then "$$" else "$" p_hsBracket :: HsBracket GhcPs -> R () p_hsBracket = \case ExpBr NoExtField expr -> do anns <- getEnclosingAnns let name = case anns of AnnOpenEQ : _ -> "" _ -> "e" quote name (located expr p_hsExpr) PatBr NoExtField pat -> located pat (quote "p" . p_pat) DecBrL NoExtField decls -> quote "d" (handleStarIsType decls (p_hsDecls Free decls)) DecBrG NoExtField _ -> notImplemented "DecBrG" -- result of renamer TypBr NoExtField ty -> quote "t" (located ty (handleStarIsType ty . p_hsType)) VarBr NoExtField isSingleQuote name -> do txt (bool "''" "'" isSingleQuote) -- HACK As you can see we use 'noLoc' here to be able to pass name into -- 'p_rdrName' since the latter expects a "located" thing. The problem -- is that 'VarBr' doesn't provide us with location of the name. This in -- turn makes it impossible to detect if there are parentheses around -- it, etc. So we have to add parentheses manually assuming they are -- necessary for all operators. let isOperator = all (\i -> isPunctuation i || isSymbol i) (showOutputable (rdrNameOcc name)) && not (doesNotNeedExtraParens name) wrapper = if isOperator then parens N else id wrapper $ p_rdrName (noLoc name) TExpBr NoExtField expr -> do txt "[||" breakpoint' located expr p_hsExpr breakpoint' txt "||]" where quote :: Text -> R () -> R () quote name body = do txt "[" txt name txt "|" breakpoint' inci $ do dontUseBraces body breakpoint' txt "|]" -- With StarIsType, type and declaration brackets might end with a *, -- so we have to insert a space in the end to prevent the (mis)parsing -- of an (*|) operator. -- The detection is a bit overcautious, as it adds the spaces as soon as -- HsStarTy is anywhere in the type/declaration. handleStarIsType :: Data a => a -> R () -> R () handleStarIsType a p | containsHsStarTy a = space *> p <* space | otherwise = p where containsHsStarTy = everything (||) $ \b -> case cast @_ @(HsType GhcPs) b of Just HsStarTy {} -> True _ -> False -- Print the source text of a string literal while indenting -- gaps correctly. p_stringLit :: String -> R () p_stringLit src = let s = splitGaps src singleLine = txt $ Text.pack (mconcat s) multiLine = sitcc $ sep breakpoint (txt . Text.pack) (backslashes s) in vlayout singleLine multiLine where -- Split a string on gaps (backslash delimited whitespaces) -- -- > splitGaps "bar\\ \\fo\\&o" == ["bar", "fo\\&o"] splitGaps :: String -> [String] splitGaps "" = [] splitGaps s = let -- A backslash and a whitespace starts a "gap" p (Just '\\', _, _) = True p (_, '\\', Just c) | ghcSpace c = False p _ = True in case span p (zipPrevNext s) of (l, r) -> let -- drop the initial '\', any amount of 'ghcSpace', and another '\' r' = drop 1 . dropWhile ghcSpace . drop 1 $ map orig r in map orig l : splitGaps r' -- GHC's definition of whitespaces in strings -- See: https://gitlab.haskell.org/ghc/ghc/blob/86753475/compiler/parser/Lexer.x#L1653 ghcSpace :: Char -> Bool ghcSpace c = c <= '\x7f' && is_space c -- Add backslashes to the inner side of the strings -- -- > backslashes ["a", "b", "c"] == ["a\\", "\\b\\", "\\c"] backslashes :: [String] -> [String] backslashes (x : y : xs) = (x ++ "\\") : backslashes (('\\' : y) : xs) backslashes xs = xs -- Attaches previous and next items to each list element zipPrevNext :: [a] -> [(Maybe a, a, Maybe a)] zipPrevNext xs = let z = zip (zip (Nothing : map Just xs) xs) (map Just (tail xs) ++ repeat Nothing) in map (\((p, x), n) -> (p, x, n)) z orig (_, x, _) = x ---------------------------------------------------------------------------- -- Helpers -- | Return the wrapping function controlling the use of braces according to -- the current layout. layoutToBraces :: Layout -> R () -> R () layoutToBraces = \case SingleLine -> useBraces MultiLine -> id -- | Append each element in both lists with semigroups. If one list is shorter -- than the other, return the rest of the longer list unchanged. liftAppend :: Semigroup a => [a] -> [a] -> [a] liftAppend [] [] = [] liftAppend [] (y : ys) = y : ys liftAppend (x : xs) [] = x : xs liftAppend (x : xs) (y : ys) = x <> y : liftAppend xs ys getGRHSSpan :: GRHS GhcPs (Located body) -> SrcSpan getGRHSSpan (GRHS NoExtField guards body) = combineSrcSpans' $ getLoc body :| map getLoc guards -- | Place a thing that may have a hanging form. This function handles how -- to separate it from preceding expressions and whether to bump indentation -- depending on what sort of expression we have. placeHanging :: Placement -> R () -> R () placeHanging placement m = case placement of Hanging -> do space m Normal -> do breakpoint inci m -- | Check if given block contains single expression which has a hanging -- form. blockPlacement :: (body -> Placement) -> [LGRHS GhcPs (Located body)] -> Placement blockPlacement placer [L _ (GRHS NoExtField _ (L _ x))] = placer x blockPlacement _ _ = Normal -- | Check if given command has a hanging form. cmdPlacement :: HsCmd GhcPs -> Placement cmdPlacement = \case HsCmdLam NoExtField _ -> Hanging HsCmdCase NoExtField _ _ -> Hanging HsCmdLamCase NoExtField _ -> Hanging HsCmdDo NoExtField _ -> Hanging _ -> Normal cmdTopPlacement :: HsCmdTop GhcPs -> Placement cmdTopPlacement (HsCmdTop NoExtField (L _ x)) = cmdPlacement x -- | Check if given expression has a hanging form. exprPlacement :: HsExpr GhcPs -> Placement exprPlacement = \case -- Only hang lambdas with single line parameter lists HsLam NoExtField mg -> case mg of MG _ (L _ [L _ (Match NoExtField _ (x : xs) _)]) _ | isOneLineSpan (combineSrcSpans' $ fmap getLoc (x :| xs)) -> Hanging _ -> Normal HsLamCase NoExtField _ -> Hanging HsCase NoExtField _ _ -> Hanging HsDo NoExtField (DoExpr _) _ -> Hanging HsDo NoExtField (MDoExpr _) _ -> Hanging OpApp NoExtField _ op y -> case (fmap getOpNameStr . getOpName . unLoc) op of Just "$" -> exprPlacement (unLoc y) _ -> Normal HsApp NoExtField _ y -> exprPlacement (unLoc y) HsProc NoExtField p _ -> -- Indentation breaks if pattern is longer than one line and left -- hanging. Consequently, only apply hanging when it is safe. if isOneLineSpan (getLoc p) then Hanging else Normal _ -> Normal withGuards :: [LGRHS GhcPs (Located body)] -> Bool withGuards = any (checkOne . unLoc) where checkOne :: GRHS GhcPs (Located body) -> Bool checkOne (GRHS NoExtField [] _) = False checkOne _ = True exprOpTree :: LHsExpr GhcPs -> OpTree (LHsExpr GhcPs) (LHsExpr GhcPs) exprOpTree (L _ (OpApp NoExtField x op y)) = OpBranch (exprOpTree x) op (exprOpTree y) exprOpTree n = OpNode n getOpName :: HsExpr GhcPs -> Maybe RdrName getOpName = \case HsVar NoExtField (L _ a) -> Just a _ -> Nothing getOpNameStr :: RdrName -> String getOpNameStr = occNameString . rdrNameOcc p_exprOpTree :: -- | Bracket style to use BracketStyle -> OpTree (LHsExpr GhcPs) (LHsExpr GhcPs) -> R () p_exprOpTree s (OpNode x) = located x (p_hsExpr' s) p_exprOpTree s (OpBranch x op y) = do let placement = opBranchPlacement exprPlacement x y -- Distinguish holes used in infix notation. -- eg. '1 _foo 2' and '1 `_foo` 2' opWrapper = case unLoc op of HsUnboundVar NoExtField _ -> backticks _ -> id ub <- opBranchBraceStyle placement let opNameStr = (fmap getOpNameStr . getOpName . unLoc) op gotDollar = opNameStr == Just "$" gotColon = opNameStr == Just ":" gotRecordDot = isRecordDot (unLoc op) (opTreeLoc y) lhs = switchLayout [opTreeLoc x] $ p_exprOpTree s x p_op = located op (opWrapper . p_hsExpr) p_y = switchLayout [opTreeLoc y] (p_exprOpTree N y) isSection = case (opTreeLoc x, getLoc op) of (RealSrcSpan treeSpan _, RealSrcSpan opSpan _) -> srcSpanEndCol treeSpan /= srcSpanStartCol opSpan _ -> False isDoBlock = \case OpNode (L _ HsDo {}) -> True _ -> False useRecordDot' <- useRecordDot if | gotColon -> do ub lhs space p_op case placement of Hanging -> do space p_y Normal -> do breakpoint inciIf (isDoBlock y) p_y | gotDollar && isOneLineSpan (opTreeLoc x) && placement == Normal -> do useBraces lhs space p_op breakpoint inci p_y | useRecordDot' && gotRecordDot -> do lhs when isSection space p_op p_y | otherwise -> do ub lhs let opAndRhs = do p_op space p_y case x of -- This case prevents an operator from being indented past the start of a `do` block -- constituting its left operand, thus altering the AST. -- This is only relevant when the `do` block is on one line, as otherwise we will -- insert a newline after `do` anyway. OpNode (unLoc -> HsDo _ _ _) | isOneLineSpan (opTreeLoc x) -> breakpoint >> opAndRhs _ -> placeHanging placement opAndRhs pattern CmdTopCmd :: HsCmd GhcPs -> LHsCmdTop GhcPs pattern CmdTopCmd cmd <- (L _ (HsCmdTop NoExtField (L _ cmd))) cmdOpTree :: LHsCmdTop GhcPs -> OpTree (LHsCmdTop GhcPs) (LHsExpr GhcPs) cmdOpTree = \case CmdTopCmd (HsCmdArrForm NoExtField op Infix _ [x, y]) -> OpBranch (cmdOpTree x) op (cmdOpTree y) n -> OpNode n p_cmdOpTree :: OpTree (LHsCmdTop GhcPs) (LHsExpr GhcPs) -> R () p_cmdOpTree = \case OpNode n -> located n p_hsCmdTop OpBranch x op y -> do let placement = opBranchPlacement cmdTopPlacement x y ub <- opBranchBraceStyle placement ub $ p_cmdOpTree x placeHanging placement $ do located op p_hsExpr space p_cmdOpTree y opBranchPlacement :: -- | Placement of nodes (ty -> Placement) -> -- | Left branch OpTree (Located ty) op -> -- | Right branch OpTree (Located ty) op -> Placement opBranchPlacement f x y -- If the beginning of the first argument and the second argument are on -- the same line, and the second argument has a hanging form, use hanging -- placement. | isOneLineSpan (mkSrcSpan (srcSpanStart (opTreeLoc x)) (srcSpanStart (opTreeLoc y))), OpNode (L _ n) <- y = f n | otherwise = Normal opBranchBraceStyle :: Placement -> R (R () -> R ()) opBranchBraceStyle placement = getLayout <&> \case SingleLine -> useBraces MultiLine -> case placement of Hanging -> useBraces Normal -> dontUseBraces -- | Return 'True' if given expression is a record-dot operator expression. isRecordDot :: -- | Operator expression HsExpr GhcPs -> -- | Span of the expression on the right-hand side of the operator SrcSpan -> Bool isRecordDot op (RealSrcSpan ySpan _) = case op of HsVar NoExtField (L (RealSrcSpan opSpan _) opName) -> (getOpNameStr opName == ".") && (srcSpanEndCol opSpan == srcSpanStartCol ySpan) _ -> False isRecordDot _ _ = False -- | Get annotations for the enclosing element. getEnclosingAnns :: R [AnnKeywordId] getEnclosingAnns = do e <- getEnclosingSpan (const True) case e of Nothing -> return [] Just e' -> getAnns (RealSrcSpan e' Nothing) -- | For use before record braces. Collapse to empty if not 'poRecordBraceSpace'. breakpointPreRecordBrace :: R () breakpointPreRecordBrace = do useSpace <- getPrinterOpt poRecordBraceSpace if useSpace then breakpoint else breakpoint' -- | For nested lists/tuples, pad with whitespace so that we always indent correctly, -- rather than sometimes indenting by 2 regardless of 'poIndentation'. p_hsExprListItem :: HsExpr GhcPs -> R () p_hsExprListItem e = do indent <- getPrinterOpt poIndentation when (listLike e) $ do getPrinterOpt poCommaStyle >>= \case Leading -> breakpoint' Trailing -> pure () vlayout (pure ()) (spaces $ indent - 2) p_hsExpr e where spaces n = txt $ Text.replicate n " " listLike = \case ExplicitList {} -> True ExplicitTuple {} -> True _ -> False