packages feed

fourmolu-0.20.0.0: src/Ormolu/Printer/Meat/Type.hs

{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedLabels #-}
{-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE TypeFamilies #-}
{-# OPTIONS_GHC -Wno-orphans #-}

-- | Rendering of types.
module Ormolu.Printer.Meat.Type
  ( p_hsType,
    p_hsTypeAnnotation,
    hasDocStrings,
    p_hsContext,
    p_hsContext',
    p_hsTyVarBndr,
    ForAllVisibility (..),
    p_forallBndrs,
    p_hsConDeclRecFields,
    p_hsConDeclField,
    p_hsConDeclFieldWithDoc,
    p_lhsTypeArg,
    p_hsSigType,
    hsSigTypeToType,
    lhsTypeToSigType,

    -- * Re-exports from Ormolu.Printer.Meat.Type.Function
    FunRepr (..),
    ParsedFunRepr,
    ParsedFunRepr' (..),
    p_hsFun,
  )
where

import Control.Monad
import Data.Choice (pattern Is, pattern With, pattern Without)
import Data.Maybe (fromMaybe)
import GHC.Data.Strict qualified as Strict
import GHC.Hs hiding (isPromoted)
import GHC.Types.SourceText
import GHC.Types.SrcLoc
import GHC.Types.Var
import Ormolu.Config
import Ormolu.Printer.Combinators
import Ormolu.Printer.Meat.Common
import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.OpTree (p_tyOpTree, tyOpTree)
import Ormolu.Printer.Meat.Declaration.StringLiteral
import {-# SOURCE #-} Ormolu.Printer.Meat.Declaration.Value (p_hsUntypedSplice)
import Ormolu.Printer.Meat.Type.Function
import Ormolu.Printer.Operators
import Ormolu.Utils
import Prelude hiding (span)

p_hsType :: HsType GhcPs -> R ()
p_hsType = \case
  ty@HsForAllTy {} ->
    p_hsFun ty
  ty@HsQualTy {} ->
    p_hsFun ty
  HsTyVar _ p n -> do
    case p of
      IsPromoted -> do
        txt "'"
        case showOutputable (unLoc n) of
          _ : '\'' : _ -> space
          _ -> return ()
      NotPromoted -> return ()
    p_rdrName n
  HsAppTy _ f x -> do
    let -- In order to format type applications with multiple parameters
        -- nicer, traverse the AST to gather the function and all the
        -- parameters together.
        gatherArgs f' knownArgs =
          case f' of
            L _ (HsAppTy _ l r) -> gatherArgs l (r : knownArgs)
            _ -> (f', knownArgs)
        (func, args) = gatherArgs f [x]
    switchLayout (getLocA f : fmap getLocA args) . sitcc $ do
      located func p_hsType
      breakpoint
      inci $
        sep breakpoint (located' p_hsType) args
  HsAppKindTy _ ty kd -> sitcc $ do
    -- The first argument is the location of the "@..." part. Not 100% sure,
    -- but I think we can ignore it as long as we use 'located' on both the
    -- type and the kind.
    located ty p_hsType
    breakpoint
    inci $ do
      txt "@"
      located kd p_hsType
  ty@HsFunTy {} ->
    p_hsFun ty
  HsListTy _ t ->
    located t (brackets N . p_hsType)
  HsTupleTy _ tsort xs ->
    let parens' =
          case tsort of
            HsUnboxedTuple -> parensHash N
            HsBoxedOrConstraintTuple -> parens N
     in parens' $ sep commaDel (sitcc . located' p_hsType) xs
  HsSumTy _ xs ->
    parensHash N $
      sep (space >> txt "|" >> breakpoint) (sitcc . located' p_hsType) xs
  HsOpTy _ _ x op y -> do
    modFixityMap <- askModuleFixityMap
    debug <- askDebug
    let opTree = BinaryOpBranches (tyOpTree x) op (tyOpTree y)
    p_tyOpTree
      (reassociateOpTree debug (Just . unLoc) modFixityMap opTree)
  HsParTy _ t -> do
    csSpans <-
      fmap (flip RealSrcSpan Strict.Nothing . getLoc) <$> getEnclosingComments
    switchLayout (locA t : csSpans) $
      parens N (sitcc $ located t p_hsType)
  HsIParamTy _ n t -> sitcc $ do
    located n atom
    inci $ p_hsTypeAnnotation t
  HsStarTy _ _ -> token'star
  HsKindSig _ t k -> sitcc $ do
    located t p_hsType
    inci $ p_hsTypeAnnotation k
  HsSpliceTy _ splice -> p_hsUntypedSplice DollarSplice splice
  HsDocTy _ t str -> do
    -- Usually handled by p_hsFun, but it's possible to have a bare type
    -- with docstrings, e.g.
    --
    --   type Name =
    --     -- | The name of a user as a string
    --     String
    --
    --   data User =
    --     User
    --       -- | Name
    --       String
    --       -- | Age
    --       Int
    withHaddocks (Is #end) (Just str) $ do
      located t p_hsType
  HsExplicitListTy _ p xs -> do
    case p of
      IsPromoted -> txt "'"
      NotPromoted -> return ()
    brackets N $ do
      -- If this list is promoted and the first element starts with a single
      -- quote, we need to put a space in between or it fails to parse.
      case (p, xs) of
        (IsPromoted, L _ t : _) | startsWithSingleQuote t -> space
        _ -> return ()
      sep commaDel (sitcc . located' p_hsType) xs
  HsExplicitTupleTy _ p xs -> do
    case p of
      IsPromoted -> txt "'"
      NotPromoted -> return ()
    parens N $ do
      -- If this tuple is promoted and the first element starts with a single
      -- quote, we need to put a space in between or it fails to parse.
      case (p, xs) of
        (IsPromoted, L _ t : _) | startsWithSingleQuote t -> space
        _ -> return ()
      sep commaDel (located' p_hsType) xs
  HsTyLit _ t ->
    case t of
      HsStrTy (SourceText s) _ -> p_stringLit s
      a -> atom a
  HsWildCardTy _ -> txt "_"
  XHsType ext -> case ext of
    HsCoreTy t -> atom @HsCoreTy t
    HsBangTy _ (HsSrcBang _ u s) t -> do
      case u of
        SrcUnpack -> txt "{-# UNPACK #-}" >> space
        SrcNoUnpack -> txt "{-# NOUNPACK #-}" >> space
        NoSrcUnpack -> return ()
      case s of
        SrcLazy -> txt "~"
        SrcStrict -> txt "!"
        NoSrcStrict -> return ()
      located t p_hsType
    HsRecTy _ fields ->
      p_hsConDeclRecFields fields
  where
    startsWithSingleQuote = \case
      HsAppTy _ (L _ f) _ -> startsWithSingleQuote f
      HsTyVar _ IsPromoted _ -> True
      HsExplicitTupleTy {} -> True
      HsExplicitListTy {} -> True
      HsTyLit _ HsCharTy {} -> True
      _ -> False

p_hsTypeAnnotation :: LHsType GhcPs -> R ()
p_hsTypeAnnotation ty =
  p_hsFunParsed @(HsType GhcPs) $
    ParsedFunSig
      { sig = (),
        next = parseFunRepr ty
      }

-- | Return 'True' if at least one argument in 'HsType' has a doc string
-- attached to it.
hasDocStrings :: HsType GhcPs -> Bool
hasDocStrings = \case
  HsDocTy {} -> True
  HsFunTy _ _ (L _ x) (L _ y) -> hasDocStrings x || hasDocStrings y
  HsForAllTy _ _ (L _ x) -> hasDocStrings x
  HsQualTy _ _ (L _ x) -> hasDocStrings x
  _ -> False

p_hsContext :: HsContext GhcPs -> R ()
p_hsContext = p_hsContext' p_hsType

p_hsConDeclRecFields :: [LHsConDeclRecField GhcPs] -> R ()
p_hsConDeclRecFields xs =
  recordBraces $ sep commaDel (sitcc . located' p_hsConDeclRecField) xs

p_hsConDeclRecField :: HsConDeclRecField GhcPs -> R ()
p_hsConDeclRecField field@HsConDeclRecField {..} = withFieldHaddocks $ do
  sitcc $
    sep
      commaDel
      (located' (p_rdrName . foLabel))
      cdrf_names
  inci $ p_hsFun field
  where
    withFieldHaddocks action = do
      commaStyle <- getPrinterOpt poCommaStyle
      let doc = cdf_doc cdrf_spec
      when (commaStyle == Trailing) $
        mapM_ (p_hsDoc Pipe (With #endNewline)) doc
      action
      when (commaStyle == Leading) $
        mapM_ (inciByFrac (-1) . (newline >>) . p_hsDoc Caret (Without #endNewline)) doc

-- | This does not print 'cdf_doc' and 'cdf_multiplicity' as there is no single
-- strategy for where to print them (see call sites).
p_hsConDeclField :: HsConDeclField GhcPs -> R ()
p_hsConDeclField CDF {..} = do
  case cdf_unpack of
    SrcUnpack -> txt "{-# UNPACK #-}" *> space
    SrcNoUnpack -> txt "{-# NOUNPACK #-}" *> space
    NoSrcUnpack -> pure ()
  located cdf_type $ \ty -> do
    case cdf_bang of
      SrcLazy -> txt "~"
      SrcStrict -> txt "!"
      NoSrcStrict -> pure ()
    p_hsType ty

p_hsConDeclFieldWithDoc :: HsConDeclField GhcPs -> R ()
p_hsConDeclFieldWithDoc cdf = withHaddocks (Is #end) cdf.cdf_doc $ do
  p_hsConDeclField cdf

p_lhsTypeArg :: LHsTypeArg GhcPs -> R ()
p_lhsTypeArg = \case
  HsValArg NoExtField ty -> located ty p_hsType
  -- first argument is the SrcSpan of the @,
  -- but the @ always has to be directly before the type argument
  HsTypeArg _ ty -> txt "@" *> located ty p_hsType
  -- NOTE(amesgen) is this unreachable or just not implemented?
  HsArgPar _ -> notImplemented "HsArgPar"

p_hsSigType :: HsSigType GhcPs -> R ()
p_hsSigType = p_hsType . hsSigTypeToType

instance FunRepr (HsType GhcPs) where
  parseFunRepr = \case
    -- `forall a. _`
    L ann (HsForAllTy _ tele ty) ->
      ParsedFunForall
        { tele = L ann tele,
          next = parseFunRepr ty
        }
    -- `HasCallStack => _`
    ty@(L _ HsQualTy {}) ->
      let (ctxs, rest) = getContexts ty
       in ParsedFunQuals
            { ctxs,
              next = parseFunRepr rest
            }
    -- `Int -> _`
    L ann (HsFunTy _ multAnn l r) ->
      let (arg, doc) =
            case l of
              L _ (HsDocTy _ x doc_) -> (x, Just doc_)
              _ -> (l, Nothing)
       in ParsedFunArg
            { span = ann,
              arg,
              doc,
              multAnn,
              next = parseFunRepr r
            }
    -- `_ -> Int`
    L _ (HsDocTy _ ret doc) -> ParsedFunReturn {ret, doc = Just doc}
    ret -> ParsedFunReturn {ret, doc = Nothing}
    where
      getContexts =
        let go ctxs = \case
              L ann (HsQualTy _ ctx ty) ->
                go (L ann ctx : ctxs) ty
              ty ->
                (reverse ctxs, ty)
         in go []

  renderFunReprCtx = p_hsType
  renderFunReprArg = p_hsType
  renderFunReprMult = p_hsType
  renderFunReprRet = p_hsType

----------------------------------------------------------------------------
-- FunRepr HsConDeclRecField

-- | FunRepr HsConDeclRecField renders a record field type annotation. If the
-- record field has a function type, we want to render it as a function
-- respecting `function-arrows`. For the most part, it behaves like HsTypeSig;
-- however, record fields can have additional syntax not in normal function
-- types, like specifying the multiplicity for the `::` or specifying
-- UNPACK/strictness, so we need to handle this specially.
instance FunRepr (HsConDeclRecField GhcPs) where
  parseFunRepr (L _ HsConDeclRecField {..}) =
    ParsedFunSig
      { sig = cdrf_spec.cdf_multiplicity,
        next = toRecFieldRepr False $ parseFunRepr cdrf_spec.cdf_type
      }
    where
      toRecFieldRepr :: Bool -> ParsedFunRepr (HsType GhcPs) -> ParsedFunRepr (HsConDeclRecField GhcPs)
      toRecFieldRepr seenArg = \case
        ParsedFunSig {} -> error "parseFunRepr @HsType unexpectedly returned ParsedFunSig"
        ParsedFunForall {..} ->
          ParsedFunForall
            { tele,
              next = toRecFieldRepr seenArg next
            }
        ParsedFunQuals {..} ->
          ParsedFunQuals
            { ctxs,
              next = toRecFieldRepr seenArg next
            }
        ParsedFunArg {..} ->
          ParsedFunArg
            { span,
              arg =
                let mUnpack =
                      if not seenArg
                        then Just cdrf_spec.cdf_unpack
                        else Nothing
                 in L (getLoc arg) (mUnpack, arg),
              doc,
              multAnn,
              next = toRecFieldRepr True next
            }
        ParsedFunReturn {..} ->
          ParsedFunReturn
            { ret =
                let mAnn =
                      if not seenArg
                        then Just (cdrf_spec.cdf_unpack, cdrf_spec.cdf_bang)
                        else Nothing
                 in L (getLoc ret) (mAnn, ret),
              doc
            }

  type FunReprSig (HsConDeclRecField GhcPs) = HsMultAnnOf (LocatedA (HsType GhcPs)) GhcPs
  renderFunReprSig multAnn = do
    space
    p_hsMultAnn (located' p_hsType) multAnn
    space
    token'dcolon

  type FunReprCtx (HsConDeclRecField GhcPs) = HsType GhcPs
  renderFunReprCtx = p_hsType

  -- Invariant: SrcUnpackedness should only be set for the first arg
  type FunReprArg (HsConDeclRecField GhcPs) = (Maybe SrcUnpackedness, LocatedA (HsType GhcPs))
  renderFunReprArg (mUnpacked, ty) =
    p_hsConDeclField
      CDF
        { cdf_ext = error "unused",
          cdf_unpack = fromMaybe NoSrcUnpack mUnpacked,
          cdf_bang = NoSrcStrict,
          cdf_multiplicity = error "unused",
          cdf_type = ty,
          cdf_doc = error "unused"
        }

  type FunReprMult (HsConDeclRecField GhcPs) = HsType GhcPs
  renderFunReprMult = p_hsType

  -- Invariant: SrcUnpackedness/SrcStrictness should only be set if there are
  -- no args
  type FunReprRet (HsConDeclRecField GhcPs) = (Maybe (SrcUnpackedness, SrcStrictness), LocatedA (HsType GhcPs))
  renderFunReprRet (mAnn, ty) =
    p_hsConDeclField
      CDF
        { cdf_ext = error "unused",
          cdf_unpack = unpack,
          cdf_bang = strictness,
          cdf_multiplicity = error "unused",
          cdf_type = ty,
          cdf_doc = error "unused"
        }
    where
      (unpack, strictness) = fromMaybe (NoSrcUnpack, NoSrcStrict) mAnn

----------------------------------------------------------------------------
-- Conversion functions

hsSigTypeToType :: HsSigType GhcPs -> HsType GhcPs
hsSigTypeToType HsSig {..} = hsOuterTyVarBndrsToHsType sig_bndrs sig_body

-- could be generalized to also handle () instead of Specificity
hsOuterTyVarBndrsToHsType ::
  HsOuterTyVarBndrs Specificity GhcPs ->
  LHsType GhcPs ->
  HsType GhcPs
hsOuterTyVarBndrsToHsType obndrs ty = case obndrs of
  HsOuterImplicit NoExtField -> unLoc ty
  HsOuterExplicit _ bndrs ->
    HsForAllTy NoExtField (mkHsForAllInvisTele noAnn bndrs) ty

lhsTypeToSigType :: LHsType GhcPs -> LHsSigType GhcPs
lhsTypeToSigType ty =
  L (getLoc ty) . HsSig NoExtField (HsOuterImplicit NoExtField) $ ty