packages feed

large-records-0.4: src/Data/Record/Internal/Plugin/Options.hs

{-# LANGUAGE DataKinds             #-}
{-# LANGUAGE DeriveDataTypeable    #-}
{-# LANGUAGE DerivingStrategies    #-}
{-# LANGUAGE LambdaCase            #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE ViewPatterns          #-}

-- | Generation options for large-records.
module Data.Record.Internal.Plugin.Options (
    -- * Definition
    LargeRecordOptions(..)
  , largeRecord
    -- * Extract options from source module
  , getLargeRecordOptions
  ) where

import Data.Bifunctor
import Data.Data (Data)
import Data.Map (Map)
import Data.Maybe (mapMaybe)
import GHC.Records.Compat

import qualified Data.Generics   as SYB
import qualified Data.Map.Strict as Map

import Data.Record.Internal.GHC.Shim
import Data.Record.Internal.GHC.TemplateHaskellStyle

{-------------------------------------------------------------------------------
  Definition
-------------------------------------------------------------------------------}

-- | A type specifying how a record should be treated by large-records.
--
-- The default for Haskell code should probably be:
--
-- > {-# ANN type T largeRecord #-}
-- > data T = ..
--
-- To see the definitions generated by @large-records@:
--
-- > {-# ANN type T largeRecord {debugLargeRecords = True} #-}
-- > data T = ..
data LargeRecordOptions = LargeRecordOptions {
      debugLargeRecords :: Bool
    }
  deriving stock (Data)

largeRecord :: LargeRecordOptions
largeRecord = LargeRecordOptions {
      debugLargeRecords = False
    }

{-------------------------------------------------------------------------------
  HasField instances

  These instances are required in modules that enable 'OverloadedRecordUpdate'
-------------------------------------------------------------------------------}

instance HasField "debugLargeRecords" LargeRecordOptions Bool where
   hasField r = (\x -> r{debugLargeRecords = x}, debugLargeRecords r)

{-------------------------------------------------------------------------------
  Extract options from module
-------------------------------------------------------------------------------}

-- | Extract all 'LargeRecordOptions' in a module
--
-- Additionally returns the location of the ANN pragma.
getLargeRecordOptions :: HsModule -> Map String [(SrcSpan, LargeRecordOptions)]
getLargeRecordOptions =
      Map.fromListWith (++)
    . map (second (:[]))
    . mapMaybe viewAnnotation
    . SYB.everything (++) (SYB.mkQ [] (:[]))

viewAnnotation :: AnnDecl GhcPs -> Maybe (String, (SrcSpan, LargeRecordOptions))
viewAnnotation = \case
    PragAnnD (TypeAnnotation tyName) (intOptions -> Just options) ->
      Just (nameBase tyName, (getLoc tyName, options))
    _otherwise ->
      Nothing

{-------------------------------------------------------------------------------
  Very limited interpreter for 'LargeRecordOptions'

  TODO: Instead of doing this, we might be able to use runAnnotation. This lives
  in the TcM monad, but the Hsc monad gives us a HscEnv which is sufficient to
  run things in the TcM monad. For that however we would need to use the
  /renamed/ module, rather than the parsed one. I think this might be possible
  now that quasi-quotation is no longer necessary, but I am not 100% sure.
-------------------------------------------------------------------------------}

intOptions :: LHsExpr GhcPs -> Maybe LargeRecordOptions
intOptions (VarE (nameBase -> "largeRecord")) =
    Just largeRecord
intOptions (RecUpdE expr fields) = do
    opts    <- intOptions expr
    updates <- mapM intUpdate fields
    return $ foldr (.) id updates opts
intOptions _ =
    Nothing

intUpdate ::
     (LRdrName, LHsExpr GhcPs)
  -> Maybe (LargeRecordOptions -> LargeRecordOptions)
intUpdate (nameBase -> "debugLargeRecords", intBool -> Just b) =
    Just $ \opts -> opts { debugLargeRecords = b }
intUpdate _otherwise =
    Nothing

intBool :: LHsExpr GhcPs -> Maybe Bool
intBool (ConE (nameBase -> "True"))  = Just True
intBool (ConE (nameBase -> "False")) = Just False
intBool _otherwise                   = Nothing