ormolu-0.7.4.0: src/Ormolu/Printer/Meat/Declaration/Value.hs
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
module Ormolu.Printer.Meat.Declaration.Value
( p_valDecl,
p_pat,
p_hsExpr,
p_hsUntypedSplice,
p_stringLit,
IsApplicand (..),
p_hsExpr',
p_hsCmdTop,
exprPlacement,
cmdTopPlacement,
)
where
import Control.Monad
import Data.Bool (bool)
import Data.Coerce (coerce)
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 Data.List.NonEmpty qualified as NE
import Data.Maybe
import Data.Text (Text)
import Data.Text qualified as Text
import Data.Void
import GHC.Data.Bag (bagToList)
import GHC.Data.FastString
import GHC.Data.Strict qualified as Strict
import GHC.Hs
import GHC.LanguageExtensions.Type (Extension (NegativeLiterals))
import GHC.Parser.CharClass (is_space)
import GHC.Types.Basic
import GHC.Types.Fixity
import GHC.Types.Name.Reader
import GHC.Types.SourceText
import GHC.Types.SrcLoc
import Language.Haskell.Syntax.Basic
import Ormolu.Printer.Combinators
import Ormolu.Printer.Meat.Common
import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration
import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.OpTree
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 (LocatedN RdrName)
| PatternBind
| Case
| Lambda
| LambdaCase
-- | Style of equations in a group.
data GroupStyle
= EqualSign
| RightArrow
p_valDecl :: HsBind GhcPs -> R ()
p_valDecl = \case
FunBind _ funId funMatches -> p_funBind funId funMatches
PatBind _ pat grhss -> p_match PatternBind False NoSrcStrict [pat] grhss
VarBind {} -> notImplemented "VarBinds" -- introduced by the type checker
PatSynBind _ psb -> p_patSynBind psb
p_funBind ::
LocatedN 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' ::
( Anno (GRHS GhcPs (LocatedA body)) ~ SrcAnn NoEpAnns,
Anno (Match GhcPs (LocatedA body)) ~ SrcSpanAnnA
) =>
-- | How to get body placement
(body -> Placement) ->
-- | How to print body
(body -> R ()) ->
-- | Style of this group of equations
MatchGroupStyle ->
-- | Match group
MatchGroup GhcPs (LocatedA 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' ::
(Anno (GRHS GhcPs (LocatedA body)) ~ SrcAnn NoEpAnns) =>
-- | 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 (LocatedA 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@(head_pat :| tail_pats) -> do
let combinedSpans = case style of
Function name -> combineSrcSpans (getLocA name) patSpans
_ -> patSpans
patSpans = combineSrcSpans' (getLocA <$> 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 head_pat of
LazyPat _ _ -> True
BangPat _ _ -> True
SplicePat _ _ -> True
_ -> False
txt "\\"
when needsSpace space
sitcc stdCase
LambdaCase -> do
located' p_pat head_pat
unless (null tail_pats) $ do
breakpoint
-- When we have multiple patterns (with `\cases`) across multiple
-- lines, we have to indent all but the first pattern.
inci $ sep breakpoint (located' p_pat) tail_pats
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 (getLocA name)
_ -> Nothing
Just pats -> (Just . getLocA . 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
Just spn
| any guardNeedsLineBreak grhssGRHSs
|| not (onTheSameLine spn grhssSpan) ->
Normal
_ -> blockPlacement placer grhssGRHSs
guardNeedsLineBreak :: XRec GhcPs (GRHS GhcPs body) -> Bool
guardNeedsLineBreak (L _ (GRHS _ guardLStmts _)) = case guardLStmts of
[] -> False
[g] -> not . isOneLineSpan . getLocA $ g
_ -> True
p_body = do
let groupStyle =
if isCase style && hasGuards
then RightArrow
else EqualSign
sep
breakpoint
(located' (p_grhs' placement placer render groupStyle))
grhssGRHSs
p_where = do
unless (eqEmptyLocalBinds grhssLocalBinds) $ do
breakpoint
txt "where"
breakpoint
inci $ p_hsLocalBinds grhssLocalBinds
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' Normal exprPlacement p_hsExpr
p_grhs' ::
-- | Placement of the parent RHS construct
Placement ->
-- | How to get body placement
(body -> Placement) ->
-- | How to print body
(body -> R ()) ->
GroupStyle ->
GRHS GhcPs (LocatedA body) ->
R ()
p_grhs' parentPlacement placer render style (GRHS _ 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 "->"
-- If we have a sequence of guards and it is placed in the normal way,
-- then we indent one level more for readability. Otherwise (all
-- guards are on the same line) we do not need to indent, as it would
-- look like double indentation without a good reason.
inciIf (parentPlacement == Normal) (placeHanging placement p_body)
where
placement =
case endOfGuards of
Nothing -> placer (unLoc body)
Just spn ->
if onTheSameLine spn (getLocA body)
then placer (unLoc body)
else Normal
endOfGuards =
case NE.nonEmpty guards of
Nothing -> Nothing
Just gs -> (Just . getLocA . NE.last) gs
p_body = located body render
p_hsCmd :: HsCmd GhcPs -> R ()
p_hsCmd = p_hsCmd' NotApplicand N
p_hsCmd' :: IsApplicand -> BracketStyle -> HsCmd GhcPs -> R ()
p_hsCmd' isApp s = \case
HsCmdArrApp _ 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 _ form Prefix _ cmds -> banana s $ do
located form p_hsExpr
unless (null cmds) $ do
breakpoint
inci (sequence_ (intersperse breakpoint (located' (p_hsCmdTop N) <$> cmds)))
HsCmdArrForm _ form Infix _ [left, right] -> do
modFixityMap <- askModuleFixityMap
debug <- askDebug
let opTree = BinaryOpBranches (cmdOpTree left) form (cmdOpTree right)
p_cmdOpTree
s
(reassociateOpTree debug (getOpName . unLoc) modFixityMap opTree)
HsCmdArrForm _ _ Infix _ _ -> notImplemented "HsCmdArrForm"
HsCmdApp _ cmd expr -> do
located cmd (p_hsCmd' Applicand s)
breakpoint
inci $ located expr p_hsExpr
HsCmdLam _ mgroup -> p_matchGroup' cmdPlacement p_hsCmd Lambda mgroup
HsCmdPar _ _ c _ -> parens N (located c p_hsCmd)
HsCmdCase _ e mgroup ->
p_case isApp cmdPlacement p_hsCmd e mgroup
HsCmdLamCase _ variant mgroup ->
p_lamcase isApp variant cmdPlacement p_hsCmd mgroup
HsCmdIf anns _ if' then' else' ->
p_if cmdPlacement p_hsCmd anns if' then' else'
HsCmdLet _ _ localBinds _ c ->
p_let p_hsCmd localBinds c
HsCmdDo _ es -> do
txt "do"
p_stmts isApp cmdPlacement (p_hsCmd' NotApplicand S) es
-- | Print a top-level command.
p_hsCmdTop :: BracketStyle -> HsCmdTop GhcPs -> R ()
p_hsCmdTop s (HsCmdTop _ cmd) = located cmd (p_hsCmd' NotApplicand s)
-- | Render an expression preserving blank lines between such consecutive
-- expressions found in the original source code.
withSpacing ::
-- | Rendering function
(a -> R ()) ->
-- | Entity to render
LocatedAn ann a ->
R ()
withSpacing f l = located l $ \x -> do
case getLocA 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' ::
( Anno (Stmt GhcPs (LocatedA body)) ~ SrcSpanAnnA,
Anno [LocatedA (Stmt GhcPs (LocatedA body))] ~ SrcSpanAnnL
) =>
-- | Placer
(body -> Placement) ->
-- | Render
(body -> R ()) ->
-- | Statement to render
Stmt GhcPs (LocatedA body) ->
R ()
p_stmt' placer render = \case
LastStmt _ body _ _ -> located body render
BindStmt _ p f@(getLocA -> l) -> do
located p p_pat
space
txt "<-"
let loc = getLocA p
placement
| isOneLineSpan (mkSrcSpan (srcSpanEnd loc) (srcSpanStart l)) = placer (unLoc f)
| otherwise = Normal
switchLayout [loc, l] $
placeHanging placement (located f render)
ApplicativeStmt {} -> notImplemented "ApplicativeStmt" -- generated by renamer
BodyStmt _ body _ _ -> located body render
LetStmt _ binds -> do
txt "let"
space
sitcc $ p_hsLocalBinds binds
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 . located recS_stmts $ sepSemi (withSpacing (p_stmt' placer render))
p_stmts ::
( Anno (Stmt GhcPs (LocatedA body)) ~ SrcSpanAnnA,
Anno [LocatedA (Stmt GhcPs (LocatedA body))] ~ SrcSpanAnnL
) =>
IsApplicand ->
-- | Placer
(body -> Placement) ->
-- | Render
(body -> R ()) ->
-- | Statements to render
LocatedL [LocatedA (Stmt GhcPs (LocatedA body))] ->
R ()
p_stmts isApp placer render es = do
breakpoint
ub <- layoutToBraces <$> getLayout
let p_stmtExt (relPos, stmt) =
ub' $ withSpacing (p_stmt' placer render) stmt
where
-- We need to set brace usage information for all but the last
-- statement (e.g.in the case of nested do blocks).
ub' = case relPos of
FirstPos -> ub
MiddlePos -> ub
LastPos -> id
SinglePos -> id
inciApplicand isApp . located es $
sepSemi p_stmtExt . attachRelativePos
gatherStmt :: ExprLStmt GhcPs -> [[ExprLStmt GhcPs]]
gatherStmt (L _ (ParStmt _ 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 :: HsLocalBinds GhcPs -> R ()
p_hsLocalBinds = \case
HsValBinds epAnn (ValBinds _ bag lsigs) -> pseudoLocated epAnn $ 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` getLocA) items
sitcc $ sepSemi p_item' (attachRelativePos binds)
HsValBinds _ _ -> notImplemented "HsValBinds"
HsIPBinds epAnn (IPBinds _ xs) -> pseudoLocated epAnn $ do
let p_ipBind (IPBind _ (L _ name) expr) = do
atom @HsIPName name
space
equals
breakpoint
useBraces $ inci $ located expr p_hsExpr
sepSemi (located' p_ipBind) xs
EmptyLocalBinds _ -> return ()
where
-- HsLocalBinds is no longer wrapped in a Located (see call sites
-- of p_hsLocalBinds). Hence, we introduce a manual Located as we
-- depend on the layout being correctly set.
pseudoLocated = \case
EpAnn {anns = AnnList {al_anchor = Just Anchor {anchor}}}
| let sp = RealSrcSpan anchor Strict.Nothing,
-- excluding cases where there are no bindings
not $ isZeroWidthSpan sp ->
located (L sp ()) . const
_ -> id
p_ldotFieldOcc :: XRec GhcPs (DotFieldOcc GhcPs) -> R ()
p_ldotFieldOcc =
located' $ p_rdrName . fmap (mkVarUnqual . field_label) . dfoLabel
p_ldotFieldOccs :: [XRec GhcPs (DotFieldOcc GhcPs)] -> R ()
p_ldotFieldOccs = sep (txt ".") p_ldotFieldOcc
p_fieldOcc :: FieldOcc GhcPs -> R ()
p_fieldOcc FieldOcc {..} = p_rdrName foLabel
p_hsFieldBind ::
(lhs ~ GenLocated l a, HasSrcSpan l) =>
(lhs -> R ()) ->
HsFieldBind lhs (LHsExpr GhcPs) ->
R ()
p_hsFieldBind p_lhs HsFieldBind {..} = do
p_lhs hfbLHS
unless hfbPun $ do
space
equals
let placement =
if onTheSameLine (getLoc' hfbLHS) (getLocA hfbRHS)
then exprPlacement (unLoc hfbRHS)
else Normal
placeHanging placement (located hfbRHS p_hsExpr)
p_hsExpr :: HsExpr GhcPs -> R ()
p_hsExpr = p_hsExpr' NotApplicand N
-- | An applicand is the left-hand side in a function application, i.e. @f@ in
-- @f a@. We need to track this in order to add extra identation in cases like
--
-- > foo =
-- > do
-- > succ
-- > 1
data IsApplicand = Applicand | NotApplicand
inciApplicand :: IsApplicand -> R () -> R ()
inciApplicand = \case
Applicand -> inci . inci
NotApplicand -> inci
p_hsExpr' :: IsApplicand -> BracketStyle -> HsExpr GhcPs -> R ()
p_hsExpr' isApp s = \case
HsVar _ name -> p_rdrName name
HsUnboundVar _ occ -> atom occ
HsRecSel _ fldOcc -> p_fieldOcc fldOcc
HsOverLabel _ sourceText _ -> do
txt "#"
p_sourceText sourceText
HsIPVar _ (HsIPName name) -> do
txt "?"
atom name
HsOverLit _ v -> atom (ol_val v)
HsLit _ lit ->
case lit of
HsString (SourceText stxt) _ -> p_stringLit stxt
HsStringPrim (SourceText stxt) _ -> p_stringLit stxt
r -> atom r
HsLam _ mgroup ->
p_matchGroup Lambda mgroup
HsLamCase _ variant mgroup ->
p_lamcase isApp variant exprPlacement p_hsExpr mgroup
HsApp _ 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' $
getLocA f :| [(srcLocSpan . srcSpanStart . getLocA) 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
ub <-
getLayout <&> \case
SingleLine -> useBraces
MultiLine -> id
ub $ do
located func (p_hsExpr' Applicand s)
breakpoint
inci $ sep breakpoint (located' p_hsExpr) initp
inci $ do
unless (null initp) breakpoint
located lastp p_hsExpr
Hanging -> do
useBraces . switchLayout [initSpan] $ do
located func (p_hsExpr' Applicand s)
breakpoint
sep breakpoint (located' p_hsExpr) initp
placeHanging placement $
located lastp p_hsExpr
HsAppType _ 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 _ x op y -> do
modFixityMap <- askModuleFixityMap
debug <- askDebug
let opTree = BinaryOpBranches (exprOpTree x) op (exprOpTree y)
p_exprOpTree
s
(reassociateOpTree debug (getOpName . unLoc) modFixityMap opTree)
NegApp _ e _ -> 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 _ _ e _ ->
parens s (located e (dontUseBraces . p_hsExpr))
SectionL _ x op -> do
located x p_hsExpr
breakpoint
inci (located op p_hsExpr)
SectionR _ op x -> do
located op p_hsExpr
breakpoint
inci (located x p_hsExpr)
ExplicitTuple _ args boxity -> do
let isSection = any isMissing args
isMissing = \case
Missing _ -> True
_ -> False
p_arg =
sitcc . \case
Present _ x -> located x p_hsExpr
Missing _ -> pure ()
parens' =
case boxity of
Boxed -> parens
Unboxed -> parensHash
enclSpan <-
fmap (flip RealSrcSpan Strict.Nothing) . maybeToList
<$> getEnclosingSpan
if isSection
then
switchLayout [] . parens' s $
sep comma p_arg args
else
switchLayout enclSpan . parens' s $
sep commaDel p_arg args
ExplicitSum _ tag arity e ->
p_unboxedSum N tag arity (located e p_hsExpr)
HsCase _ e mgroup ->
p_case isApp exprPlacement p_hsExpr e mgroup
HsIf anns if' then' else' ->
p_if exprPlacement p_hsExpr anns if' then' else'
HsMultiIf _ guards -> do
txt "if"
breakpoint
inciApplicand isApp $ sep newline (located' (p_grhs RightArrow)) guards
HsLet _ _ localBinds _ e ->
p_let p_hsExpr localBinds e
HsDo _ doFlavor es -> do
let doBody moduleName header = do
forM_ moduleName $ \m -> atom m *> txt "."
txt header
p_stmts isApp exprPlacement (p_hsExpr' NotApplicand S) es
compBody = brackets s . located es $ \xs -> do
let p_parBody =
sep
(breakpoint >> txt "|" >> space)
p_seqBody
p_seqBody =
sitcc
. sep
commaDel
(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 doFlavor of
DoExpr moduleName -> doBody moduleName "do"
MDoExpr moduleName -> doBody moduleName "mdo"
ListComp -> compBody
MonadComp -> compBody
GhciStmtCtxt -> notImplemented "GhciStmtCtxt"
ExplicitList _ xs ->
brackets s $
sep commaDel (sitcc . located' p_hsExpr) xs
RecordCon {..} -> do
p_rdrName rcon_con
breakpoint
let HsRecFields {..} = rcon_flds
p_lhs = located' $ p_rdrName . foLabel
fields = located' (p_hsFieldBind p_lhs) <$> 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
breakpoint
let p_updLbl =
located' $
p_rdrName . \case
(Unambiguous NoExtField n :: AmbiguousFieldOcc GhcPs) -> n
Ambiguous NoExtField n -> n
p_recFields p_lbl =
sep commaDel (sitcc . located' (p_hsFieldBind p_lbl))
inci . braces N $ case rupd_flds of
RegularRecUpdFields {..} ->
p_recFields p_updLbl recUpdFields
OverloadedRecUpdFields {..} ->
p_recFields (located' (coerce p_ldotFieldOccs)) olRecUpdFields
HsGetField {..} -> do
located gf_expr p_hsExpr
txt "."
p_ldotFieldOcc gf_field
HsProjection {..} -> parens N $ do
txt "."
p_ldotFieldOccs (NE.toList proj_flds)
ExprWithTySig _ x HsWC {hswc_body} -> sitcc $ do
located x p_hsExpr
space
txt "::"
breakpoint
inci $ located hswc_body p_hsSigType
ArithSeq _ _ 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
HsTypedBracket _ expr -> do
txt "[||"
breakpoint'
located expr p_hsExpr
breakpoint'
txt "||]"
HsUntypedBracket epAnn x -> p_hsQuote epAnn x
HsTypedSplice _ expr -> p_hsSpliceTH True expr DollarSplice
HsUntypedSplice _ untySplice -> p_hsUntypedSplice DollarSplice untySplice
HsProc _ p e -> do
txt "proc"
located p $ \x -> do
breakpoint
inci (p_pat x)
breakpoint
txt "->"
placeHanging (cmdTopPlacement (unLoc e)) $
located e (p_hsCmdTop N)
HsStatic _ e -> do
txt "static"
breakpoint
inci (located e p_hsExpr)
HsPragE _ prag x -> case prag of
HsPragSCC _ name -> do
txt "{-# SCC "
atom name
txt " #-}"
breakpoint
let inciIfS = case s of N -> id; S -> inci
inciIfS $ located x p_hsExpr
p_patSynBind :: PatSynBind GhcPs GhcPs -> R ()
p_patSynBind PSB {..} = do
let rhs conSpans = do
space
let pattern_def_spans = [getLocA psb_id, getLocA psb_def] ++ conSpans
case psb_dir of
Unidirectional ->
switchLayout pattern_def_spans $ do
txt "<-"
breakpoint
located psb_def p_pat
ImplicitBidirectional ->
switchLayout pattern_def_spans $ do
equals
breakpoint
located psb_def p_pat
ExplicitBidirectional mgroup -> do
switchLayout pattern_def_spans $ 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
let conSpans = getLocA <$> xs
switchLayout conSpans $ do
unless (null xs) breakpoint
sitcc (sep breakpoint p_rdrName xs)
rhs conSpans
PrefixCon (v : _) _ -> absurd v
RecCon xs -> do
space
p_rdrName psb_id
inci $ do
let conSpans = getLocA . recordPatSynPatVar <$> xs
switchLayout conSpans $ do
unless (null xs) breakpoint
braces N $
sep commaDel (p_rdrName . recordPatSynPatVar) xs
rhs conSpans
InfixCon l r -> do
let conSpans = [getLocA l, getLocA r]
switchLayout conSpans $ do
space
p_rdrName l
breakpoint
inci $ do
p_rdrName psb_id
space
p_rdrName r
inci (rhs conSpans)
p_case ::
( Anno (GRHS GhcPs (LocatedA body)) ~ SrcAnn NoEpAnns,
Anno (Match GhcPs (LocatedA body)) ~ SrcSpanAnnA
) =>
IsApplicand ->
-- | Placer
(body -> Placement) ->
-- | Render
(body -> R ()) ->
-- | Expression
LHsExpr GhcPs ->
-- | Match group
MatchGroup GhcPs (LocatedA body) ->
R ()
p_case isApp placer render e mgroup = do
txt "case"
space
located e p_hsExpr
space
txt "of"
breakpoint
inciApplicand isApp (p_matchGroup' placer render Case mgroup)
p_lamcase ::
( Anno (GRHS GhcPs (LocatedA body)) ~ SrcAnn NoEpAnns,
Anno (Match GhcPs (LocatedA body)) ~ SrcSpanAnnA
) =>
IsApplicand ->
-- | Variant (@\\case@ or @\\cases@)
LamCaseVariant ->
-- | Placer
(body -> Placement) ->
-- | Render
(body -> R ()) ->
-- | Expression
MatchGroup GhcPs (LocatedA body) ->
R ()
p_lamcase isApp variant placer render mgroup = do
txt $ case variant of
LamCase -> "\\case"
LamCases -> "\\cases"
breakpoint
inciApplicand isApp (p_matchGroup' placer render LambdaCase mgroup)
p_if ::
-- | Placer
(body -> Placement) ->
-- | Render
(body -> R ()) ->
-- | Annotations
EpAnn AnnsIf ->
-- | If
LHsExpr GhcPs ->
-- | Then
LocatedA body ->
-- | Else
LocatedA body ->
R ()
p_if placer render epAnn if' then' else' = do
txt "if"
space
located if' p_hsExpr
breakpoint
inci $ do
locatedToken thenSpan "then"
space
placeHangingLocated thenSpan then'
breakpoint
locatedToken elseSpan "else"
space
placeHangingLocated elseSpan else'
where
(thenSpan, elseSpan, commentSpans) =
case epAnn of
EpAnn {anns = AnnsIf {aiThen, aiElse}, comments} ->
( loc' $ epaLocationRealSrcSpan aiThen,
loc' $ epaLocationRealSrcSpan aiElse,
map (anchor . getLoc) $
case comments of
EpaComments cs -> cs
EpaCommentsBalanced pre post -> pre <> post
)
EpAnnNotUsed ->
(noSrcSpan, noSrcSpan, [])
locatedToken tokenSpan token =
located (L tokenSpan ()) $ \_ -> txt token
betweenSpans spanA spanB s = spanA < s && s < spanB
placeHangingLocated tokenSpan bodyLoc@(L _ body) = do
let bodySpan = getLoc' bodyLoc
hasComments = fromMaybe False $ do
tokenRealSpan <- srcSpanToRealSrcSpan tokenSpan
bodyRealSpan <- srcSpanToRealSrcSpan bodySpan
pure $ any (betweenSpans tokenRealSpan bodyRealSpan) commentSpans
placement = if hasComments then Normal else placer body
switchLayout [tokenSpan, bodySpan] $
placeHanging placement (located bodyLoc render)
p_let ::
-- | Render
(body -> R ()) ->
HsLocalBinds GhcPs ->
LocatedA body ->
R ()
p_let render localBinds e = sitcc $ do
txt "let"
space
dontUseBraces $ sitcc (p_hsLocalBinds localBinds)
vlayout space (newline >> txt " ")
txt "in"
space
sitcc (located e render)
p_pat :: Pat GhcPs -> R ()
p_pat = \case
WildPat _ -> txt "_"
VarPat _ name -> p_rdrName name
LazyPat _ pat -> do
txt "~"
located pat p_pat
AsPat _ name _ pat -> do
p_rdrName name
txt "@"
located pat p_pat
ParPat _ _ pat _ ->
located pat (parens S . p_pat)
BangPat _ pat -> do
txt "!"
located pat p_pat
ListPat _ pats ->
brackets S $ sep commaDel (located' p_pat) pats
TuplePat _ pats boxing -> do
let parens' =
case boxing of
Boxed -> parens S
Unboxed -> parensHash S
parens' $ sep commaDel (sitcc . located' p_pat) pats
SumPat _ pat tag arity ->
p_unboxedSum S tag arity (located pat p_pat)
ConPat _ pat details ->
case details of
PrefixCon tys xs -> sitcc $ do
p_rdrName pat
unless (null tys && null xs) breakpoint
inci . sitcc $
sep breakpoint (sitcc . either p_hsConPatTyArg (located' p_pat)) $
(Left <$> tys) <> (Right <$> xs)
RecCon (HsRecFields fields dotdot) -> do
p_rdrName pat
breakpoint
let f = \case
Nothing -> txt ".."
Just x -> located x p_pat_hsFieldBind
inci . braces N . sep commaDel f $
case dotdot of
Nothing -> Just <$> fields
Just (L _ (RecFieldsDotDot n)) -> (Just <$> take n fields) ++ [Nothing]
InfixCon l r -> do
switchLayout [getLocA l, getLocA r] $ do
located l p_pat
breakpoint
inci $ do
p_rdrName pat
space
located r p_pat
ViewPat _ expr pat -> sitcc $ do
located expr p_hsExpr
space
txt "->"
breakpoint
inci (located pat p_pat)
SplicePat _ splice -> p_hsUntypedSplice DollarSplice splice
LitPat _ p -> atom p
NPat _ v (isJust -> isNegated) _ -> do
when isNegated $ do
txt "-"
negativeLiterals <- isExtensionEnabled NegativeLiterals
when negativeLiterals space
located v (atom . ol_val)
NPlusKPat _ n k _ _ _ -> sitcc $ do
p_rdrName n
breakpoint
inci $ do
txt "+"
space
located k (atom . ol_val)
SigPat _ pat HsPS {..} -> do
located pat p_pat
p_typeAscription (lhsTypeToSigType hsps_body)
p_hsPatSigType :: HsPatSigType GhcPs -> R ()
p_hsPatSigType (HsPS _ ty) = txt "@" *> located ty p_hsType
p_hsConPatTyArg :: HsConPatTyArg GhcPs -> R ()
p_hsConPatTyArg (HsConPatTyArg _ patSigTy) = p_hsPatSigType patSigTy
p_pat_hsFieldBind :: HsRecField GhcPs (LPat GhcPs) -> R ()
p_pat_hsFieldBind HsFieldBind {..} = do
located hfbLHS p_fieldOcc
unless hfbPun $ do
space
equals
breakpoint
inci (located hfbRHS 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_hsUntypedSplice :: SpliceDecoration -> HsUntypedSplice GhcPs -> R ()
p_hsUntypedSplice deco = \case
HsUntypedSpliceExpr _ expr -> p_hsSpliceTH False expr deco
HsQuasiQuote _ quoterName str -> do
txt "["
p_rdrName (noLocA quoterName)
txt "|"
-- QuasiQuoters often rely on precise custom strings. We cannot do any
-- formatting here without potentially breaking someone's code.
atom str
txt "|]"
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_hsQuote :: EpAnn [AddEpAnn] -> HsQuote GhcPs -> R ()
p_hsQuote epAnn = \case
ExpBr _ expr -> do
let name
| any isJust (matchAddEpAnn AnnOpenEQ <$> epAnnAnns epAnn) = ""
| otherwise = "e"
quote name (located expr p_hsExpr)
PatBr _ pat -> located pat (quote "p" . p_pat)
DecBrL _ decls -> quote "d" (handleStarIsType decls (p_hsDecls Free decls))
DecBrG _ _ -> notImplemented "DecBrG" -- result of renamer
TypBr _ ty -> quote "t" (located ty (handleStarIsType ty . p_hsType))
VarBr _ isSingleQuote name -> do
txt (bool "''" "'" isSingleQuote)
p_rdrName name
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 :: FastString -> R ()
p_stringLit src =
let s = splitGaps (unpackFS 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 =
zip3 (Nothing : map Just xs) xs (map Just (drop 1 xs) ++ [Nothing])
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 (LocatedA body) -> SrcSpan
getGRHSSpan (GRHS _ guards body) =
combineSrcSpans' $ getLocA body :| map getLocA guards
-- | Determine placement of a given block.
blockPlacement ::
(body -> Placement) ->
[LGRHS GhcPs (LocatedA body)] ->
Placement
blockPlacement placer [L _ (GRHS _ _ (L _ x))] = placer x
blockPlacement _ _ = Normal
-- | Determine placement of a given command.
cmdPlacement :: HsCmd GhcPs -> Placement
cmdPlacement = \case
HsCmdLam _ _ -> Hanging
HsCmdCase _ _ _ -> Hanging
HsCmdLamCase _ _ _ -> Hanging
HsCmdDo _ _ -> Hanging
_ -> Normal
-- | Determine placement of a top level command.
cmdTopPlacement :: HsCmdTop GhcPs -> Placement
cmdTopPlacement (HsCmdTop _ (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 _ mg -> case mg of
MG _ (L _ [L _ (Match _ _ (x : xs) _)])
| isOneLineSpan (combineSrcSpans' $ fmap getLocA (x :| xs)) ->
Hanging
_ -> Normal
HsLamCase _ _ _ -> Hanging
HsCase _ _ _ -> Hanging
HsDo _ (DoExpr _) _ -> Hanging
HsDo _ (MDoExpr _) _ -> Hanging
OpApp _ _ op y ->
case (fmap getOpNameStr . getOpName . unLoc) op of
Just "$" -> exprPlacement (unLoc y)
_ -> Normal
HsApp _ _ y -> exprPlacement (unLoc y)
HsProc _ p _ ->
-- Indentation breaks if pattern is longer than one line and left
-- hanging. Consequently, only apply hanging when it is safe.
if isOneLineSpan (getLocA p)
then Hanging
else Normal
_ -> Normal
-- | Return 'True' if any of the RHS expressions has guards.
withGuards :: [LGRHS GhcPs body] -> Bool
withGuards = any (checkOne . unLoc)
where
checkOne (GRHS _ [] _) = False
checkOne _ = True