packages feed

hindent-6.3.0: src/HIndent/Ast/Expression.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ViewPatterns #-}

module HIndent.Ast.Expression
  ( Expression
  , GuardExpression
  , mkExpression
  , mkGuardExpression
  ) where

import Control.Monad
import Control.Monad.RWS (gets)
import Data.List.NonEmpty (NonEmpty(..), (<|))
import qualified Data.List.NonEmpty as NonEmpty
import Data.Monoid (First(..))
import qualified GHC.Hs as GHC
import qualified GHC.Types.Basic as GHC
import qualified GHC.Types.Fixity as GHC
import qualified GHC.Types.SrcLoc as GHC
import HIndent.Ast.Cmd (Cmd, CmdDoBlock, mkCmdDoBlock, mkCmdFromHsCmdTop)
import HIndent.Ast.Expression.Bracket (Bracket, mkBracket)
import HIndent.Ast.Expression.FieldSelector (FieldSelector, mkFieldSelector)
import qualified HIndent.Ast.Expression.ListComprehension as LC
import HIndent.Ast.Expression.OverloadedLabel
  ( OverloadedLabel
  , mkOverloadedLabel
  )
import HIndent.Ast.Expression.Pragmatic (ExpressionPragma, mkExpressionPragma)
import HIndent.Ast.Expression.RangeExpression
  ( RangeExpression
  , mkRangeExpression
  )
import HIndent.Ast.Expression.RecordConstructionField
  ( RecordConstructionFields
  , mkRecordConstructionFields
  )
import HIndent.Ast.Expression.RecordUpdateField
  ( RecordUpdateFields
  , mkRecordUpdateFields
  )
import HIndent.Ast.Guard (Guard, mkMultiWayIfExprGuard)
import HIndent.Ast.LocalBinds (LocalBinds, mkLocalBinds)
import HIndent.Ast.MatchGroup (MatchGroup, hasMatches, mkExprMatchGroup)
import HIndent.Ast.Module.Name (mkModuleName)
import HIndent.Ast.Name.Prefix
import HIndent.Ast.NodeComments (NodeComments(..))
import HIndent.Ast.Pattern
import HIndent.Ast.Statement (ExprStatement, mkExprStatement)
import HIndent.Ast.Type
import HIndent.Ast.Type.ImplicitParameterName
  ( ImplicitParameterName
  , mkImplicitParameterName
  )
import HIndent.Ast.WithComments
import HIndent.CabalFile ()
import {-# SOURCE #-} HIndent.Pretty (Pretty(..), pretty)
import HIndent.Pretty.Combinators
import HIndent.Pretty.NodeComments
import HIndent.Pretty.Types (DoOrMdo(..), QualifiedDo(..))
import HIndent.Printer
import qualified Language.Haskell.Syntax.Basic as HS
#if MIN_VERSION_ghc_lib_parser(9, 6, 1)
import Data.Maybe
import HIndent.Ast.Expression.Splice (Splice, mkSplice, mkTypedSplice)
import HIndent.Fixity (fixities)
import qualified Language.Haskell.GhclibParserEx.GHC.Hs.Expr as GHC
#else
import qualified GHC.Types.Name.Reader as NameReader
import HIndent.Ast.Expression.Splice (Splice, mkSplice)
#endif
data Expression
  = Variable (WithComments PrefixName)
  | UnboundVariable PrefixName
  | OverloadedLabel OverloadedLabel
  | ImplicitParameter ImplicitParameterName
  | OverloadedLiteral (GHC.HsOverLit GHC.GhcPs)
  | Literal (GHC.HsLit GHC.GhcPs)
  | Lambda MatchGroup
  | LambdaCase
      { usesCases :: Bool
      , matches :: MatchGroup
      }
  | Application (NonEmpty (WithComments Expression))
  | TypeApplication
      { expression :: WithComments Expression
      , typeArg :: WithComments Type
      }
  | OperatorApplication
      { firstOperand :: WithComments Expression
      , operatorOperandPairs :: NonEmpty
          (WithComments InfixExpr, WithComments Expression)
      }
  | Negation (WithComments Expression)
  | Parenthesized (WithComments Expression)
  | LeftSection
      { left :: WithComments Expression
      , operator :: WithComments InfixExpr
      }
  | RightSection
      { operator :: WithComments InfixExpr
      , right :: WithComments Expression
      }
  | Tuple
      { arguments :: [WithComments (Maybe (WithComments Expression))]
      , isBoxed :: Bool
      }
  | Sum
      { position :: GHC.ConTag
      , arity :: HS.SumWidth
      , expression :: WithComments Expression
      }
  | CaseExpression
      { scrutinee :: WithComments Expression
      , matches :: MatchGroup
      }
  | IfExpression
      { predicate :: WithComments Expression
      , thenBranch :: WithComments Expression
      , elseBranch :: WithComments Expression
      }
  | MultiIf [WithComments Guard]
  | LetBinding
      { bindings :: WithComments LocalBinds
      , expression :: WithComments Expression
      }
  | DoBlock
      { qualifiedDo :: QualifiedDo
      , statements :: WithComments [WithComments ExprStatement]
      }
  | ListComprehension (WithComments LC.ListComprehension)
  | ListLiteral [WithComments Expression]
  | RecordConstruction
      { name :: WithComments PrefixName
      , fields :: RecordConstructionFields
      }
  | RecordUpdate RecordUpdateFields
  | FieldProjection
      { expression :: WithComments Expression
      , selector :: WithComments FieldSelector
      }
  | Projection (NonEmpty (WithComments FieldSelector))
  | TypeSignature
      { expression :: WithComments Expression
      , signature :: WithComments Type
      }
  | ArithmeticSequence RangeExpression
  | TypedQuotation (WithComments Expression)
  | UntypedQuotation Bracket
  | Splice Splice
  | ProcExpression
      { pat :: WithComments Pattern
      , cmd :: WithComments Cmd
      }
  | ProcDo
      { pat :: WithComments Pattern
      , block :: CmdDoBlock
      }
  | StaticExpression (WithComments Expression)
  | PragmaticExpression
      { pragma :: WithComments ExpressionPragma
      , expression :: WithComments Expression
      }

instance CommentExtraction Expression where
  nodeComments _ = NodeComments [] [] []

instance Pretty Expression where
  pretty' (Variable name) = pretty name
  pretty' (UnboundVariable name) = pretty name
  pretty' (OverloadedLabel label) = pretty label
  pretty' (ImplicitParameter name) = pretty name
  pretty' (OverloadedLiteral lit) = pretty lit
  pretty' (Literal lit) = pretty lit
  pretty' (Lambda matches) = pretty matches
  pretty' LambdaCase {..} = do
    string
      $ if usesCases
          then "\\cases"
          else "\\case"
    if not $ hasMatches matches
      then string " {}"
      else do
        newline
        indentedBlock $ pretty matches
  pretty' (Application (headExpr :| argList)) = horizontal <-|> vertical
    where
      horizontal = spaced $ pretty <$> headExpr : argList
      vertical = do
        col <- gets psColumn
        indentSpaces <- getIndentSpaces
        pretty headExpr
        col' <- gets psColumn
        let headLength =
              col'
                - col
                - if col == 0
                    then indentSpaces
                    else 0
            hangFirstArg = headLength + 1 <= indentSpaces
        if hangFirstArg
          then space
          else newline
        spaces' <- getIndentSpaces
        indentedWithSpace spaces' $ lined $ fmap pretty argList
  pretty' TypeApplication {..} = do
    pretty expression
    string " @"
    pretty typeArg
  pretty' OperatorApplication {..} = horizontal <-|> vertical
    where
      horizontal = do
        pretty firstOperand
        space
        spaced
          $ concatMap (\(op, r) -> [pretty op, pretty r]) operatorOperandPairs
      vertical = do
        pretty firstOperand
        prettyOpAndRhs $ NonEmpty.toList operatorOperandPairs
        where
          prettyOpAndRhs [] = pure ()
          prettyOpAndRhs [(o, r)]
            | shouldBeInline (getNode r) = space >> spaced [pretty o, pretty r]
          prettyOpAndRhs ((o, r):xs) = do
            newline
            indentedBlock $ (pretty o >> space) |=> pretty r
            prettyOpAndRhs xs
          shouldBeInline DoBlock {} = True
          shouldBeInline Lambda {} = True
          shouldBeInline LambdaCase {} = True
          shouldBeInline _ = False
  pretty' (Negation expr) = string "-" >> pretty expr
  pretty' (Parenthesized expr) = parens $ pretty expr
  pretty' LeftSection {..} = spaced [pretty left, pretty operator]
  pretty' RightSection {..} = (pretty operator >> space) |=> pretty right
  pretty' Tuple {..} = horizontal <-|> vertical
    where
      horizontal = parH $ prettyArg pretty <$> arguments
      vertical =
        parV
          $ prefixedLined ","
          $ prettyArg (\expr -> space |=> pretty expr) <$> arguments
      prettyArg f arg = prettyWith arg (maybe (pure ()) f)
      (parH, parV) =
        if isBoxed
          then (hTuple, parens)
          else (hUnboxedTuple, unboxedParens)
  pretty' Sum {..} = do
    string "(#"
    forM_ [1 .. arity] $ \idx -> do
      if idx == position
        then space >> pretty expression >> space
        else space
      when (idx < arity) $ string "|"
    string "#)"
  pretty' CaseExpression {..} = do
    string "case " |=> do
      pretty scrutinee
      string " of"
    if not $ hasMatches matches
      then string " {}"
      else do
        newline
        indentedBlock $ pretty matches
  pretty' IfExpression {..} = do
    string "if " |=> pretty predicate
    indentedBlock
      $ newlinePrefixed [branch "then " thenBranch, branch "else " elseBranch]
    where
      branch str exprWithComments =
        prettyWith exprWithComments $ \expr ->
          case expr of
            DoBlock {} -> do
              string str
              pretty expr
            _ -> string str |=> pretty expr
  pretty' (MultiIf multiIfClauses) =
    string "if " |=> lined (fmap pretty multiIfClauses)
  pretty' LetBinding {..} =
    lined
      [string "let " |=> pretty bindings, string " in " |=> pretty expression]
  pretty' DoBlock {..} = do
    pretty qualifiedDo
    newline
    indentedBlock $ prettyWith statements (lined . fmap pretty)
  pretty' (ListComprehension listComprehension) = pretty listComprehension
  pretty' (ListLiteral listItems) = horizontal <-|> vertical
    where
      horizontal = brackets $ hCommaSep $ fmap pretty listItems
      vertical = vList $ fmap pretty listItems
  pretty' RecordConstruction {..} = horizontal <-|> vertical
    where
      horizontal = spaced [pretty name, pretty fields]
      vertical = do
        pretty name
        (space >> pretty fields) <-|> (newline >> indentedBlock (pretty fields))
  pretty' (RecordUpdate fields) = pretty fields
  pretty' FieldProjection {..} = do
    pretty expression
    dot
    pretty selector
  pretty' (Projection projectionFields) =
    parens $ forM_ projectionFields prettyProjectionField
    where
      prettyProjectionField projectionField = do
        string "."
        pretty projectionField
  pretty' TypeSignature {..} =
    spaced [pretty expression, string "::", pretty signature]
  pretty' (ArithmeticSequence rangeExpression) = pretty rangeExpression
  pretty' (TypedQuotation expr) = typedBrackets $ pretty expr
  pretty' (UntypedQuotation bracket) = pretty bracket
  pretty' ProcExpression {..} = hor <-|> ver
    where
      hor = spaced [string "proc", pretty pat, string "->", pretty cmd]
      ver = do
        spaced [string "proc", pretty pat, string "->"]
        newline
        indentedBlock $ pretty cmd
  pretty' ProcDo {..} = do
    spaced [string "proc", pretty pat, string "-> do"]
    newline
    indentedBlock $ pretty block
  pretty' (StaticExpression expr) = spaced [string "static", pretty expr]
  pretty' PragmaticExpression {..} = spaced [pretty pragma, pretty expression]
  pretty' (Splice splice') = pretty splice'

mkExpression :: GHC.HsExpr GHC.GhcPs -> Expression
mkExpression (GHC.HsVar _ name) =
  Variable $ mkPrefixName <$> fromGenLocated name
#if MIN_VERSION_ghc_lib_parser(9, 6, 0)
mkExpression (GHC.HsUnboundVar _ name) = UnboundVariable $ mkPrefixName name
#else
mkExpression (GHC.HsUnboundVar _ name) =
  UnboundVariable $ mkPrefixName $ NameReader.mkRdrUnqual name
#endif
#if MIN_VERSION_ghc_lib_parser(9, 12, 1)
mkExpression (GHC.HsOverLabel _ label) =
  OverloadedLabel $ mkOverloadedLabel label
#elif MIN_VERSION_ghc_lib_parser(9, 6, 1)
mkExpression (GHC.HsOverLabel _ _ label) =
  OverloadedLabel $ mkOverloadedLabel label
#else
mkExpression (GHC.HsOverLabel _ label) =
  OverloadedLabel $ mkOverloadedLabel label
#endif
mkExpression (GHC.HsIPVar _ name) =
  ImplicitParameter $ mkImplicitParameterName name
mkExpression (GHC.HsOverLit _ lit) = OverloadedLiteral lit
mkExpression (GHC.HsLit _ lit) = Literal lit
#if MIN_VERSION_ghc_lib_parser(9, 10, 1)
mkExpression (GHC.HsEmbTy _ _) =
  error "`ghc-lib-parser` never generates this AST node."
#endif
#if MIN_VERSION_ghc_lib_parser(9, 10, 1)
mkExpression (GHC.HsLam _ GHC.LamSingle matches) =
  Lambda $ mkExprMatchGroup matches
mkExpression (GHC.HsLam _ GHC.LamCases matches) =
  LambdaCase {usesCases = True, matches = mkExprMatchGroup matches}
mkExpression (GHC.HsLam _ GHC.LamCase matches) =
  LambdaCase {usesCases = False, matches = mkExprMatchGroup matches}
#else
mkExpression (GHC.HsLam _ matches) = Lambda $ mkExprMatchGroup matches
#if MIN_VERSION_ghc_lib_parser(9, 4, 1)
mkExpression (GHC.HsLamCase _ GHC.LamCases matches) =
  LambdaCase {usesCases = True, matches = mkExprMatchGroup matches}
mkExpression (GHC.HsLamCase _ GHC.LamCase matches) =
  LambdaCase {usesCases = False, matches = mkExprMatchGroup matches}
#else
mkExpression (GHC.HsLamCase _ _ matches) =
  LambdaCase {usesCases = False, matches = mkExprMatchGroup matches}
#endif
#endif
mkExpression (GHC.HsApp _ function argument) =
  case getNode f of
    Application (h :| as) -> Application $ h :| push as
    _ -> Application $ f :| [a]
  where
    f = mkExpression <$> fromGenLocated function
    a = mkExpression <$> fromGenLocated argument
    push [] = [a]
    push (x:xs) = go x xs
      where
        go final [] = [addComments (getComments f) final, a]
        go current (y:ys) = current : go y ys
#if MIN_VERSION_ghc_lib_parser(9, 10, 1)
mkExpression (GHC.HsAppType _ fun typeArg) =
  TypeApplication
    { expression = mkExpression <$> fromGenLocated fun
    , typeArg = mkTypeFromLHsWcType typeArg
    }
#else
mkExpression (GHC.HsAppType _ fun _ typeArg) =
  TypeApplication
    { expression = mkExpression <$> fromGenLocated fun
    , typeArg = mkTypeFromLHsWcType typeArg
    }
#endif
mkExpression (GHC.OpApp _ lhs op rhs) = OperatorApplication {..}
  where
    (firstOperand, operatorOperandPairs) =
      case fixityDir operatorFixity of
        GHC.InfixL -> build leftChain
        GHC.InfixR -> build rightChain
        GHC.InfixN
          | GHC.L _ (GHC.OpApp _ _ o _) <- lhs
          , isSameAssoc o -> build leftChain
          | otherwise -> build rightChain
    operatorFixity = findFixity op
    build (x:xs) = (mkExpression <$> fromGenLocated x, f xs)
      where
        f [l, o] =
          NonEmpty.singleton
            ( InfixExpr . mkExpression <$> fromGenLocated l
            , mkExpression <$> fromGenLocated o)
        f (l:o:rs) =
          ( InfixExpr . mkExpression <$> fromGenLocated l
          , mkExpression <$> fromGenLocated o)
            <| f rs
        f _ = error "Malformed operator chain"
    build _ = error "Empty operator chain"
    findFixity o =
      fromMaybe GHC.defaultFixity $ lookup (GHC.varToStr o) fixities
    leftChain = reverse $ rhs : op : collect lhs
      where
        collect :: GHC.LHsExpr GHC.GhcPs -> [GHC.LHsExpr GHC.GhcPs]
        collect (GHC.L _ (GHC.OpApp _ l o r))
          | isSameAssoc o = r : o : collect l
        collect x = [x]
    rightChain = lhs : op : collect rhs
      where
        collect :: GHC.LHsExpr GHC.GhcPs -> [GHC.LHsExpr GHC.GhcPs]
        collect (GHC.L _ (GHC.OpApp _ l o r))
          | isSameAssoc o = l : o : collect r
        collect x = [x]
    isSameAssoc (findFixity -> fixity) =
      fixityLevel fixity == level && fixityDir fixity == dir
    level = fixityLevel operatorFixity
    dir = fixityDir operatorFixity
#if MIN_VERSION_ghc_lib_parser(9, 12, 1)
    fixityLevel (GHC.Fixity lvl _) = lvl
    
    fixityDir (GHC.Fixity _ direction) = direction
#else
    fixityLevel (GHC.Fixity _ lvl _) = lvl
    
    fixityDir (GHC.Fixity _ _ direction) = direction
#endif
mkExpression (GHC.NegApp _ expr _) =
  Negation $ mkExpression <$> fromGenLocated expr
#if MIN_VERSION_ghc_lib_parser(9, 10, 1)
mkExpression (GHC.HsPar _ expr) =
  Parenthesized $ mkExpression <$> fromGenLocated expr
#elif MIN_VERSION_ghc_lib_parser(9, 8, 1)
mkExpression (GHC.HsPar _ _ expr _) =
  Parenthesized $ mkExpression <$> fromGenLocated expr
#else
mkExpression (GHC.HsPar _ expr) =
  Parenthesized $ mkExpression <$> fromGenLocated expr
#endif
mkExpression (GHC.SectionL _ l r) =
  LeftSection
    { left = mkExpression <$> fromGenLocated l
    , operator = InfixExpr . mkExpression <$> fromGenLocated r
    }
mkExpression (GHC.SectionR _ l r) =
  RightSection
    { operator = InfixExpr . mkExpression <$> fromGenLocated l
    , right = mkExpression <$> fromGenLocated r
    }
#if MIN_VERSION_ghc_lib_parser(9, 10, 1)
mkExpression (GHC.ExplicitTuple _ tupleArgs boxity) =
  Tuple
    { arguments = mkTupleArgument <$> tupleArgs
    , isBoxed =
        case boxity of
          GHC.Boxed -> True
          GHC.Unboxed -> False
    }
  where
    mkTupleArgument (GHC.Present _ expr) =
      mkWithComments $ Just $ mkExpression <$> fromGenLocated expr
    mkTupleArgument (GHC.Missing ann) = fromEpAnn ann Nothing
#else
mkExpression (GHC.ExplicitTuple _ tupleArgs boxity) =
  Tuple
    { arguments = mkTupleArgument <$> tupleArgs
    , isBoxed =
        case boxity of
          GHC.Boxed -> True
          GHC.Unboxed -> False
    }
  where
    mkTupleArgument (GHC.Present ann expr) =
      fromEpAnn ann $ Just $ mkExpression <$> fromGenLocated expr
    mkTupleArgument (GHC.Missing ann) = fromEpAnn ann Nothing
#endif
mkExpression (GHC.ExplicitSum _ position arity expr) =
  Sum {expression = mkExpression <$> fromGenLocated expr, ..}
mkExpression (GHC.HsCase _ scrut matches) =
  CaseExpression
    { scrutinee = mkExpression <$> fromGenLocated scrut
    , matches = mkExprMatchGroup matches
    }
mkExpression (GHC.HsIf _ predicateExpr thenExpr elseExpr) =
  IfExpression
    { predicate = mkExpression <$> fromGenLocated predicateExpr
    , thenBranch = mkExpression <$> fromGenLocated thenExpr
    , elseBranch = mkExpression <$> fromGenLocated elseExpr
    }
mkExpression (GHC.HsMultiIf _ clauses) =
  MultiIf (fmap (fmap mkMultiWayIfExprGuard . fromGenLocated) clauses)
#if MIN_VERSION_ghc_lib_parser(9, 10, 1)
mkExpression (GHC.HsLet _ localBinds body) =
  case mkLocalBinds localBinds of
    Nothing ->
      error
        "`ghc-lib-parser` never generates a `HsLet` node with empty bindings."
    Just bindings ->
      LetBinding {expression = mkExpression <$> fromGenLocated body, ..}
#elif MIN_VERSION_ghc_lib_parser(9, 8, 1)
mkExpression (GHC.HsLet _ _ localBinds _ body) =
  case mkLocalBinds localBinds of
    Nothing ->
      error
        "`ghc-lib-parser` never generates a `HsLet` node with empty bindings."
    Just bindings ->
      LetBinding {expression = mkExpression <$> fromGenLocated body, ..}
#else
mkExpression (GHC.HsLet _ localBinds body) =
  case mkLocalBinds localBinds of
    Nothing ->
      error
        "`ghc-lib-parser` never generates a `HsLet` node with empty bindings."
    Just bindings ->
      LetBinding {expression = mkExpression <$> fromGenLocated body, ..}
#endif
mkExpression (GHC.HsDo _ GHC.ListComp statements) =
  ListComprehension
    $ LC.mkListComprehension . fmap (fmap mkExprStatement . fromGenLocated)
        <$> fromGenLocated statements
mkExpression (GHC.HsDo _ GHC.MonadComp statements) =
  ListComprehension
    $ LC.mkListComprehension . fmap (fmap mkExprStatement . fromGenLocated)
        <$> fromGenLocated statements
mkExpression (GHC.HsDo _ (GHC.DoExpr moduleName) statements) =
  DoBlock
    { qualifiedDo = QualifiedDo (fmap mkModuleName moduleName) Do
    , statements =
        fmap (fmap mkExprStatement . fromGenLocated)
          <$> fromGenLocated statements
    }
mkExpression (GHC.HsDo _ (GHC.MDoExpr moduleName) statements) =
  DoBlock
    { qualifiedDo = QualifiedDo (fmap mkModuleName moduleName) Mdo
    , statements =
        fmap (fmap mkExprStatement . fromGenLocated)
          <$> fromGenLocated statements
    }
mkExpression (GHC.HsDo _ GHC.GhciStmtCtxt {} _) =
  error "`ghc-lib-parser` never generates this AST node."
mkExpression (GHC.ExplicitList _ items) =
  ListLiteral (fmap (fmap mkExpression . fromGenLocated) items)
mkExpression (GHC.RecordCon { GHC.rcon_con = conName
                            , GHC.rcon_flds = recordFields
                            }) =
  RecordConstruction
    { name = mkPrefixName <$> fromGenLocated conName
    , fields = mkRecordConstructionFields recordFields
    }
mkExpression (GHC.RecordUpd { GHC.rupd_expr = recordExpr
                            , GHC.rupd_flds = updates
                            }) =
  RecordUpdate
    $ mkRecordUpdateFields (mkExpression <$> fromGenLocated recordExpr) updates
mkExpression (GHC.HsGetField {GHC.gf_expr = fieldExpr, GHC.gf_field = selector}) =
  FieldProjection
    { expression = mkExpression <$> fromGenLocated fieldExpr
    , selector = fmap mkFieldSelector (fromGenLocated selector)
    }
#if MIN_VERSION_ghc_lib_parser(9, 12, 1)
mkExpression (GHC.HsProjection {GHC.proj_flds = fields}) =
  Projection $ fmap (mkWithComments . mkFieldSelector) fields
#else
mkExpression (GHC.HsProjection {GHC.proj_flds = fields}) =
  Projection $ fmap (fmap mkFieldSelector . fromGenLocated) fields
#endif
mkExpression (GHC.ExprWithTySig _ signatureExpr ty) =
  TypeSignature
    { expression = mkExpression <$> fromGenLocated signatureExpr
    , signature =
        flattenComments
          $ mkTypeFromHsSigType <$> fromGenLocated (GHC.hswc_body ty)
    }
mkExpression (GHC.ArithSeq _ _ info) =
  ArithmeticSequence $ mkRangeExpression info
mkExpression (GHC.HsTypedBracket _ typedExpr) =
  TypedQuotation $ mkExpression <$> fromGenLocated typedExpr
mkExpression (GHC.HsUntypedBracket _ content) =
  UntypedQuotation $ mkBracket content
#if MIN_VERSION_ghc_lib_parser(9, 12, 2)
mkExpression GHC.HsForAll {} =
  error "`ghc-lib-parser` never generates this AST node."
mkExpression GHC.HsQual {} =
  error "`ghc-lib-parser` never generates this AST node."
mkExpression GHC.HsFunArr {} =
  error "`ghc-lib-parser` never generates this AST node."
#endif
#if MIN_VERSION_ghc_lib_parser(9, 6, 1)
mkExpression (GHC.HsTypedSplice _ expr) = Splice $ mkTypedSplice expr
mkExpression (GHC.HsUntypedSplice _ splice) = Splice $ mkSplice splice
#else
mkExpression (GHC.HsSpliceE _ splice) = Splice $ mkSplice splice
mkExpression (GHC.HsTypedSplice _ expr) = Splice $ mkSplice expr
mkExpression (GHC.HsUntypedSplice _ splice) = Splice $ mkSplice splice
#endif
mkExpression (GHC.HsProc _ pat command) =
  case getFirst $ foldMap (First . mkCmdDoBlock) cmd of
    Just block -> ProcDo {pat = mkPattern <$> fromGenLocated pat, block}
    Nothing -> ProcExpression {pat = mkPattern <$> fromGenLocated pat, cmd}
  where
    cmd = flattenComments $ mkCmdFromHsCmdTop <$> fromGenLocated command
mkExpression (GHC.HsStatic _ staticExpr) =
  StaticExpression $ mkExpression <$> fromGenLocated staticExpr
mkExpression (GHC.HsPragE _ pragma pragmaticExpr) =
  PragmaticExpression
    { expression = mkExpression <$> fromGenLocated pragmaticExpr
    , pragma = mkExpressionPragma pragma
    }
#if !MIN_VERSION_ghc_lib_parser(9, 12, 1)
mkExpression (GHC.HsRecSel _ _) =
  error "`ghc-lib-parser` never generates this AST node."
#endif
newtype InfixExpr =
  InfixExpr Expression

instance CommentExtraction InfixExpr where
  nodeComments _ = NodeComments [] [] []

instance Pretty InfixExpr where
  pretty' (InfixExpr (Variable name)) = pretty $ mkPrefixAsInfix <$> name
  pretty' (InfixExpr (UnboundVariable name)) = pretty $ mkPrefixAsInfix name
  pretty' (InfixExpr (Parenthesized inner)) = pretty $ fmap InfixExpr inner
  pretty' (InfixExpr x) = pretty x

data GuardExpression
  = GuardWithDo Expression
  | GuardWithAppAndDo Expression
  | GuardExpression Expression

instance CommentExtraction GuardExpression where
  nodeComments (GuardWithDo expr) = nodeComments expr
  nodeComments (GuardWithAppAndDo expr) = nodeComments expr
  nodeComments (GuardExpression expr) = nodeComments expr

instance Pretty GuardExpression where
  pretty' (GuardWithDo expr) = space >> pretty expr
  pretty' (GuardWithAppAndDo expr) = space >> pretty expr
  pretty' (GuardExpression expr) = horizontal <-|> vertical
    where
      horizontal = space >> pretty expr
      vertical = newline >> indentedBlock (pretty expr)

mkGuardExpression :: Expression -> GuardExpression
mkGuardExpression expr@DoBlock {} = GuardWithDo expr
mkGuardExpression expr
  | OperatorApplication {..} <- expr
  , DoBlock {} <- getNode firstOperand = GuardWithAppAndDo expr
  | otherwise = GuardExpression expr