packages feed

morley-1.12.0: src/Util/TH.hs

-- SPDX-FileCopyrightText: 2020 Tocqueville Group
--
-- SPDX-License-Identifier: LicenseRef-MIT-TQ

module Util.TH
  ( deriveGADTNFData
  , lookupTypeNameOrFail
  ) where

import Language.Haskell.TH

-- | Generates an NFData instance for a GADT. /Note:/ This will not generate
-- additional constraints to the generated instance if those are required.
deriveGADTNFData :: Name -> Q [Dec]
deriveGADTNFData name = do
  (TyConI (DataD _ dataName vars _ cons _)) <- reify name
  let
    getNameFromVar (PlainTV n) = n
    getNameFromVar (KindedTV n _) = n
    convertTyVars orig = foldr (\a b -> AppT b . VarT $ getNameFromVar a) orig vars

    -- Unfolds multiple constructors of form "A, B, C :: A -> Stuff"
    -- into a list of tuples of constructor names and their data
    unfoldConstructor (GadtC cs bangs _) = map (,bangs) cs
    unfoldConstructor (ForallC _ _ c) = unfoldConstructor c
    unfoldConstructor _ = fail "Non GADT constructors are not supported."

    -- Constructs a clause "rnf (ConName a1 a2 ...) = rnf (a1, a2, ...)
    makeClauses (conName, bangs) = do
      varNames <- traverse (\_ -> newName "a") bangs
      let rnfExp e = AppE (VarE $ mkName "rnf") e
      return $
        (Clause
          [ConP conName $ map VarP varNames]
          (NormalB (rnfExp . TupE $ map VarE varNames))
          []
        )

    makeInstance clauses =
      InstanceD
        Nothing
        []
        (AppT (ConT $ mkName "NFData") (convertTyVars $ ConT dataName))
        [FunD (mkName "rnf") clauses]

  clauses <- traverse makeClauses $ cons >>= unfoldConstructor
  return [makeInstance clauses]

lookupTypeNameOrFail :: String -> Q Name
lookupTypeNameOrFail typeStr =
  lookupTypeName typeStr >>= \case
    Nothing -> fail $ "Failed type name lookup for: '" <> typeStr <> "'."
    Just tn -> pure tn