{-# LANGUAGE LambdaCase #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns #-} module Ormolu.Printer.Meat.Declaration.Value ( p_valDecl, p_pat, p_hsExpr, p_hsSplice, p_stringLit, ) where import Bag (bagToList) import BasicTypes import Control.Monad import Ctype (is_space) import Data.Bool (bool) import Data.Char (isPunctuation, isSymbol) import Data.Data hiding (Infix, Prefix) import Data.Functor ((<&>)) import Data.List (intersperse, sortOn) import Data.List.NonEmpty ((<|), NonEmpty (..)) import qualified Data.List.NonEmpty as NE import Data.Text (Text) import qualified Data.Text as Text import GHC import OccName (mkVarOcc) 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) 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 XHsBindsLR x -> noExtCon x 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 {..} = do let ob = case style of Case -> id LambdaCase -> id _ -> dontUseBraces -- 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 p_Match (XMatch x) = noExtCon x p_matchGroup' _ _ _ (XMatchGroup x) = noExtCon x -- | 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 "~" inci' <- case NE.nonEmpty m_pats of Nothing -> id <$ case style of Function name -> p_rdrName name _ -> return () Just ne_pats -> do let combinedSpans = combineSrcSpans' $ getLoc <$> ne_pats inci' = if isOneLineSpan combinedSpans then id else inci switchLayout [combinedSpans] $ do let stdCase = sep breakpoint (located' p_pat) m_pats case style of Function name -> p_infixDefHelper isInfix inci' (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 inci' 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 let 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 = GHC.isEmptyLocalBindsPR (unLoc grhssLocalBinds) unless (GHC.eqEmptyLocalBinds (unLoc grhssLocalBinds)) $ do breakpoint txt "where" unless whereIsEmpty breakpoint inci $ located grhssLocalBinds p_hsLocalBinds inci' $ 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_match' _ _ _ _ _ _ (XGRHSs x) = noExtCon x 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 (comma >> breakpoint) (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_grhs' _ _ _ (XGRHS x) = noExtCon x p_hsCmd :: HsCmd GhcPs -> R () p_hsCmd = \case HsCmdArrApp NoExtField body input arrType _ -> do located body p_hsExpr breakpoint inci $ do case arrType of HsFirstOrderApp -> txt "-<" HsHigherOrderApp -> txt "-<<" placeHanging (exprPlacement (unLoc input)) $ located input p_hsExpr HsCmdArrForm NoExtField form Prefix _ cmds -> banana $ sitcc $ do located form p_hsExpr unless (null cmds) $ do breakpoint inci (sequence_ (intersperse breakpoint (located' p_hsCmdTop <$> cmds))) HsCmdArrForm NoExtField form Infix _ [left, right] -> do located left p_hsCmdTop space located form p_hsExpr placeHanging (cmdTopPlacement (unLoc right)) $ located right p_hsCmdTop HsCmdArrForm NoExtField _ Infix _ _ -> notImplemented "HsCmdArrForm" HsCmdApp {} -> -- XXX Does this ever occur in the syntax tree? It does not seem like it -- does. Open an issue and ping @yumiova if this ever occurs in output. notImplemented "HsCmdApp" HsCmdLam NoExtField mgroup -> p_matchGroup' cmdPlacement p_hsCmd Lambda mgroup HsCmdPar NoExtField c -> parens N (located c p_hsCmd) HsCmdCase NoExtField e mgroup -> p_case cmdPlacement p_hsCmd e 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" newline inci . located es $ sitcc . sep newline (sitcc . withSpacing (p_stmt' cmdPlacement p_hsCmd)) HsCmdWrap {} -> notImplemented "HsCmdWrap" XCmd x -> noExtCon x p_hsCmdTop :: HsCmdTop GhcPs -> R () p_hsCmdTop = \case HsCmdTop NoExtField cmd -> located cmd p_hsCmd XCmdTop x -> noExtCon x withSpacing :: Data a => (a -> R ()) -> 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 _ _ -> do located p p_pat space txt "<-" let loc = getLoc p placement = case f of L l' x -> if isOneLineSpan (mkSrcSpan (srcSpanEnd loc) (srcSpanStart l')) then placer x else Normal switchLayout [loc, getLoc f] $ 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 XStmtLR c -> noExtCon c 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 gatherStmtBlock (XParStmtBlock x) = noExtCon x p_hsLocalBinds :: HsLocalBindsLR GhcPs GhcPs -> R () p_hsLocalBinds = \case HsValBinds NoExtField (ValBinds NoExtField bag lsigs) -> do let ssStart = either (srcSpanStart . getLoc) (srcSpanStart . getLoc) items = (Left <$> bagToList bag) ++ (Right <$> lsigs) p_item (Left x) = located x p_valDecl p_item (Right x) = located x p_sigDecl -- 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 sitcc $ sepSemi ( \(p, i) -> ( case p of SinglePos -> id FirstPos -> br MiddlePos -> br LastPos -> id ) (p_item i) ) (attachRelativePos $ sortOn ssStart items) 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 _) _" p_ipBind (XIPBind x) = noExtCon x in sepSemi (located' p_ipBind) xs HsIPBinds NoExtField _ -> notImplemented "HsIpBinds" EmptyLocalBinds NoExtField -> return () XHsLocalBindsLR x -> noExtCon x 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_hsTupArg :: HsTupArg GhcPs -> R () p_hsTupArg = \case Present NoExtField x -> located x p_hsExpr Missing NoExtField -> pure () XTupArg x -> noExtCon x 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 v -> atom (unboundVarOcc v) HsConLikeOut NoExtField _ -> notImplemented "HsConLikeOut" HsRecFld NoExtField x -> case x of Unambiguous NoExtField name -> p_rdrName name Ambiguous NoExtField name -> p_rdrName name XAmbiguousFieldOcc xx -> noExtCon xx 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 -> do txt "\\case" breakpoint inci (p_matchGroup LambdaCase 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 itself is a -- do-block or case expression it is not a good idea. It seems -- to be safe to always bump indentation when the function -- expression is parenthesised. indent = case func of L _ (HsPar NoExtField _) -> inci L _ (HsAppType NoExtField _ _) -> inci L _ (HsMultiIf NoExtField _) -> inci L spn _ -> if isOneLineSpan spn then inci else id ub <- getLayout <&> \case SingleLine -> useBraces MultiLine -> id ub $ do located func (p_hsExpr' s) breakpoint indent $ sep breakpoint (located' p_hsExpr) initp indent $ 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 $ located lastp p_hsExpr HsAppType NoExtField e a -> do located e p_hsExpr breakpoint inci $ do txt "@" located (hswc_body a) p_hsType OpApp NoExtField x op y -> do let opTree = OpBranch (exprOpTree x) op (exprOpTree y) p_exprOpTree True s (reassociateOpTree getOpName opTree) NegApp NoExtField e _ -> do txt "-" space located e p_hsExpr HsPar NoExtField e -> parens s (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 -> do let isSection = any (isMissing . unLoc) args isMissing = \case Missing NoExtField -> True _ -> False let parens' = case boxity of Boxed -> parens Unboxed -> parensHash if isSection then switchLayout [] . parens' s $ sep comma (located' p_hsTupArg) args else switchLayout (getLoc <$> args) . parens' s . sitcc $ sep (comma >> breakpoint) (sitcc . located' p_hsTupArg) 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 . sitcc $ sep newline (located' (p_grhs RightArrow)) guards HsLet NoExtField localBinds e -> p_let p_hsExpr localBinds e HsDo NoExtField ctx es -> do let doBody header = do txt header breakpoint ub <- layoutToBraces <$> getLayout inci $ sepSemi (ub . withSpacing (p_stmt' exprPlacement (p_hsExpr' S))) (unLoc es) compBody = brackets N $ located es $ \xs -> do let p_parBody = sep (breakpoint >> txt "| ") p_seqBody p_seqBody = sitcc . sep (comma >> breakpoint) (located' (sitcc . p_stmt)) 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 -> doBody "do" MDoExpr -> doBody "mdo" ListComp -> compBody MonadComp -> notImplemented "MonadComp" ArrowExpr -> notImplemented "ArrowExpr" GhciStmtCtxt -> notImplemented "GhciStmtCtxt" PatGuard _ -> notImplemented "PatGuard" ParStmtCtxt _ -> notImplemented "ParStmtCtxt" TransStmtCtxt _ -> notImplemented "TransStmtCtxt" ExplicitList _ _ xs -> brackets s . sitcc $ sep (comma >> breakpoint) (sitcc . located' p_hsExpr) xs RecordCon {..} -> do located rcon_con_name atom breakpoint let HsRecFields {..} = rcon_flds updName f = (f :: HsRecField GhcPs (LHsExpr GhcPs)) { hsRecFieldLbl = case unLoc $ hsRecFieldLbl f of FieldOcc _ n -> n XFieldOcc x -> noExtCon x } fields = located' (p_hsRecField . updName) <$> rec_flds dotdot = case rec_dotdot of Just {} -> [txt ".."] Nothing -> [] inci . braces N . sitcc $ sep (comma >> breakpoint) 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)) unless (useRecordDot' && isPluginForm) breakpoint let updName f = (f :: HsRecUpdField GhcPs) { hsRecFieldLbl = case unLoc $ hsRecFieldLbl f of Ambiguous _ n -> n Unambiguous _ n -> n XAmbiguousFieldOcc x -> noExtCon x } inci . braces N . sitcc $ sep (comma >> breakpoint) (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 ExprWithTySig NoExtField _ HsWC {hswc_body = XHsImplicitBndrs x} -> noExtCon x ExprWithTySig NoExtField _ (XHsWildCardBndrs x) -> noExtCon x ArithSeq NoExtField _ x -> case x of From from -> brackets s . sitcc $ do located from p_hsExpr breakpoint txt ".." FromThen from next -> brackets s . sitcc $ do sitcc $ sep (comma >> breakpoint) (located' p_hsExpr) [from, next] breakpoint txt ".." FromTo from to -> brackets s . sitcc $ do located from p_hsExpr breakpoint txt ".." space located to p_hsExpr FromThenTo from next to -> brackets s . sitcc $ do sitcc $ sep (comma >> breakpoint) (located' p_hsExpr) [from, next] breakpoint txt ".." space located to p_hsExpr HsSCC NoExtField _ name x -> do txt "{-# SCC " atom name txt " #-}" breakpoint located x p_hsExpr HsCoreAnn NoExtField _ value x -> do txt "{-# CORE " atom value txt " #-}" breakpoint located x 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" HsTickPragma {} -> notImplemented "HsTickPragma" HsWrap {} -> notImplemented "HsWrap" XExpr x -> noExtCon x 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 newline txt "where" newline 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) breakpoint braces N . sitcc $ sep (comma >> breakpoint) (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_patSynBind (XPatSynBind x) = noExtCon x 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_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 . p_pat) BangPat NoExtField pat -> do txt "!" located pat p_pat ListPat NoExtField pats -> brackets S . sitcc $ sep (comma >> breakpoint) (located' p_pat) pats TuplePat NoExtField pats boxing -> do let f = case boxing of Boxed -> parens S Unboxed -> parensHash S f . sitcc $ sep (comma >> breakpoint) (sitcc . located' p_pat) pats SumPat NoExtField pat tag arity -> p_unboxedSum S tag arity (located pat p_pat) ConPatIn 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 breakpoint let f = \case Nothing -> txt ".." Just x -> located x p_pat_hsRecField inci . braces N . sitcc . sep (comma >> breakpoint) 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 ConPatOut {} -> notImplemented "ConPatOut" -- presumably created by renamer? 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 _ _ -> 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 hswc -> do located pat p_pat p_typeAscription hswc CoPat {} -> notImplemented "CoPat" -- apparently created at some later stage XPat x -> noExtCon x 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, i) = do let isFirst = i == 0 isLast = i == arity - 1 case x :: Maybe (R ()) of Nothing -> unless (isFirst || isLast) space Just m' -> do unless isFirst space m' unless isLast space parensHash s $ sep (txt "|") f (zip args [0 ..]) 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" HsSplicedT {} -> notImplemented "HsSplicedT" XSplice x -> noExtCon x p_hsSpliceTH :: -- | Typed splice? Bool -> -- | Splice expression LHsExpr GhcPs -> -- | Splice decoration SpliceDecoration -> R () p_hsSpliceTH isTyped expr = \case HasParens -> do txt decoSymbol parens N (located expr (sitcc . p_hsExpr)) HasDollar -> do txt decoSymbol located expr (sitcc . p_hsExpr) NoParens -> 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" (p_hsDecls Free decls) DecBrG NoExtField _ -> notImplemented "DecBrG" -- result of renamer TypBr NoExtField ty -> quote "t" (located 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 "||]" XBracket x -> noExtCon x where quote :: Text -> R () -> R () quote name body = do txt "[" txt name txt "|" breakpoint' inci $ do dontUseBraces body breakpoint' txt "|]" -- 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 getGRHSSpan (XGRHS x) = noExtCon x -- | 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 HsCmdDo NoExtField _ -> Hanging _ -> Normal cmdTopPlacement :: HsCmdTop GhcPs -> Placement cmdTopPlacement = \case HsCmdTop NoExtField (L _ x) -> cmdPlacement x XCmdTop x -> noExtCon 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 -- If the rightmost expression in an operator chain is hanging, make the -- whole block hanging; so that we can use the common @f = foo $ do@ -- style. OpApp NoExtField _ _ y -> exprPlacement (unLoc y) -- Same thing for function applications (usually with -XBlockArguments) 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 p_exprOpTree :: -- | Can use special handling of dollar? Bool -> -- | Bracket style to use BracketStyle -> OpTree (LHsExpr GhcPs) (LHsExpr GhcPs) -> R () p_exprOpTree _ s (OpNode x) = located x (p_hsExpr' s) p_exprOpTree isDollarSpecial s (OpBranch x op y) = do -- 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. let placement = if isOneLineSpan (mkSrcSpan (srcSpanStart (opTreeLoc x)) (srcSpanStart (opTreeLoc y))) then case y of OpNode (L _ n) -> exprPlacement n _ -> Normal else Normal -- Distinguish holes used in infix notation. -- eg. '1 _foo 2' and '1 `_foo` 2' opWrapper = case unLoc op of HsUnboundVar NoExtField _ -> backticks _ -> id layout <- getLayout let ub = case layout of SingleLine -> useBraces MultiLine -> case placement of Hanging -> useBraces Normal -> dontUseBraces gotDollar = case getOpName (unLoc op) of Just rname -> mkVarOcc "$" == rdrNameOcc rname _ -> False lhs = switchLayout [opTreeLoc x] $ p_exprOpTree (not gotDollar) s x let p_op = located op (opWrapper . p_hsExpr) p_y = switchLayout [opTreeLoc y] (p_exprOpTree True N y) isSection = case (opTreeLoc x, getLoc op) of (RealSrcSpan treeSpan, RealSrcSpan opSpan) -> srcSpanEndCol treeSpan /= srcSpanStartCol opSpan _ -> False useRecordDot' <- useRecordDot let isRecordDot' = isRecordDot (unLoc op) (opTreeLoc y) if useRecordDot' && isRecordDot' then do lhs when isSection space p_op p_y else if isDollarSpecial && gotDollar && placement == Normal && isOneLineSpan (opTreeLoc x) then do useBraces lhs space p_op breakpoint inci p_y else 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 -- | 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) -> isDot opName && (srcSpanEndCol opSpan == srcSpanStartCol ySpan) _ -> False isRecordDot _ _ = False -- | Check whether a given 'RdrName' is the dot operator. isDot :: RdrName -> Bool isDot name = rdrNameOcc name == mkVarOcc "." -- | 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')