content: {-# LANGUAGE LambdaCase #-}
  {-# HLINT ignore "Unused LANGUAGE pragma" #-}
  {-# LANGUAGE MultiWayIf #-}
  {-# LANGUAGE TemplateHaskell #-}
  {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
  
  -- |
  -- Module      :   Grisette.Internal.TH.Derivation.Derive
  -- Copyright   :   (c) Sirui Lu 2024
  -- License     :   BSD-3-Clause (see the LICENSE file)
  --
  -- Maintainer  :   siruilu@cs.washington.edu
  -- Stability   :   Experimental
  -- Portability :   GHC only
  module Grisette.Internal.TH.Derivation.Derive
    ( derive,
      deriveWith,
      allClasses0,
      allClasses01,
      allClasses012,
      basicClasses0,
      basicClasses1,
      basicClasses2,
      basicClasses01,
      basicClasses012,
      noExistentialClasses0,
      concreteOrdClasses0,
      hashableClasses0,
      noExistentialClasses1,
      concreteOrdClasses1,
      hashableClasses1,
      noExistentialClasses2,
      concreteOrdClasses2,
      hashableClasses2,
      showClasses,
      pprintClasses,
      evalSymClasses,
      extractSymClasses,
      substSymClasses,
      allSymsClasses,
      eqClasses,
      ordClasses,
      symOrdClasses,
      symEqClasses,
      unifiedSymOrdClasses,
      unifiedSymEqClasses,
      mergeableClasses,
      nfDataClasses,
      hashableClasses,
      toSymClasses,
      toConClasses,
      serialClasses,
      simpleMergeableClasses,
      unifiedSimpleMergeableClasses,
      filterExactNumArgs,
      filterLeqNumArgs,
    )
  where
  
  import Control.Arrow (Arrow (second))
  import Control.DeepSeq (NFData, NFData1, NFData2)
  import Data.Binary (Binary)
  import Data.Bytes.Serial (Serial, Serial1, Serial2)
  import Data.Functor.Classes (Eq1, Eq2, Ord1, Ord2, Show1, Show2)
  import Data.Hashable (Hashable)
  import Data.Hashable.Lifted (Hashable1, Hashable2)
  import qualified Data.Map as M
  import Data.Serialize (Serialize)
  import qualified Data.Set as S
  import Grisette.Internal.Internal.Decl.Core.Data.Class.EvalSym
    ( EvalSym,
      EvalSym1,
      EvalSym2,
    )
  import Grisette.Internal.Internal.Decl.Core.Data.Class.ExtractSym
    ( ExtractSym,
      ExtractSym1,
      ExtractSym2,
    )
  import Grisette.Internal.Internal.Decl.Core.Data.Class.Mergeable
    ( Mergeable,
      Mergeable1,
      Mergeable2,
      Mergeable3,
    )
  import Grisette.Internal.Internal.Decl.Core.Data.Class.PPrint
    ( PPrint,
      PPrint1,
      PPrint2,
    )
  import Grisette.Internal.Internal.Decl.Core.Data.Class.SimpleMergeable
    ( SimpleMergeable,
      SimpleMergeable1,
      SimpleMergeable2,
    )
  import Grisette.Internal.Internal.Decl.Core.Data.Class.SubstSym
    ( SubstSym,
      SubstSym1,
      SubstSym2,
    )
  import Grisette.Internal.Internal.Decl.Core.Data.Class.SymEq
    ( SymEq,
      SymEq1,
      SymEq2,
    )
  import Grisette.Internal.Internal.Decl.Core.Data.Class.SymOrd
    ( SymOrd,
      SymOrd1,
      SymOrd2,
    )
  import Grisette.Internal.Internal.Decl.Core.Data.Class.ToCon
    ( ToCon,
      ToCon1,
      ToCon2,
    )
  import Grisette.Internal.Internal.Decl.Core.Data.Class.ToSym
    ( ToSym,
      ToSym1,
      ToSym2,
    )
  import Grisette.Internal.Internal.Decl.SymPrim.AllSyms
    ( AllSyms,
      AllSyms1,
      AllSyms2,
    )
  import Grisette.Internal.Internal.Decl.Unified.Class.UnifiedSimpleMergeable
    ( UnifiedSimpleMergeable,
      UnifiedSimpleMergeable1,
      UnifiedSimpleMergeable2,
    )
  import Grisette.Internal.Internal.Decl.Unified.Class.UnifiedSymEq
    ( UnifiedSymEq,
      UnifiedSymEq1,
      UnifiedSymEq2,
    )
  import Grisette.Internal.Internal.Decl.Unified.Class.UnifiedSymOrd
    ( UnifiedSymOrd,
      UnifiedSymOrd1,
      UnifiedSymOrd2,
    )
  import Grisette.Internal.TH.Derivation.Common
    ( DeriveConfig
        ( evalModeConfig,
          needExtraMergeableUnderEvalMode,
          needExtraMergeableWithConcretizedEvalMode
        ),
      EvalModeConfig (EvalModeConstraints, EvalModeSpecified),
    )
  import Grisette.Internal.TH.Derivation.DeriveAllSyms
    ( deriveAllSyms,
      deriveAllSyms1,
      deriveAllSyms2,
    )
  import Grisette.Internal.TH.Derivation.DeriveBinary (deriveBinary)
  import Grisette.Internal.TH.Derivation.DeriveCereal (deriveCereal)
  import Grisette.Internal.TH.Derivation.DeriveEq
    ( deriveEq,
      deriveEq1,
      deriveEq2,
    )
  import Grisette.Internal.TH.Derivation.DeriveEvalSym
    ( deriveEvalSym,
      deriveEvalSym1,
      deriveEvalSym2,
    )
  import Grisette.Internal.TH.Derivation.DeriveExtractSym
    ( deriveExtractSym,
      deriveExtractSym1,
      deriveExtractSym2,
    )
  import Grisette.Internal.TH.Derivation.DeriveHashable
    ( deriveHashable,
      deriveHashable1,
      deriveHashable2,
    )
  import Grisette.Internal.TH.Derivation.DeriveMergeable (genMergeableList)
  import Grisette.Internal.TH.Derivation.DeriveNFData
    ( deriveNFData,
      deriveNFData1,
      deriveNFData2,
    )
  import Grisette.Internal.TH.Derivation.DeriveOrd
    ( deriveOrd,
      deriveOrd1,
      deriveOrd2,
    )
  import Grisette.Internal.TH.Derivation.DerivePPrint
    ( derivePPrint,
      derivePPrint1,
      derivePPrint2,
    )
  import Grisette.Internal.TH.Derivation.DeriveSerial
    ( deriveSerial,
      deriveSerial1,
      deriveSerial2,
    )
  import Grisette.Internal.TH.Derivation.DeriveShow
    ( deriveShow,
      deriveShow1,
      deriveShow2,
    )
  import Grisette.Internal.TH.Derivation.DeriveSimpleMergeable
    ( deriveSimpleMergeable,
      deriveSimpleMergeable1,
      deriveSimpleMergeable2,
    )
  import Grisette.Internal.TH.Derivation.DeriveSubstSym
    ( deriveSubstSym,
      deriveSubstSym1,
      deriveSubstSym2,
    )
  import Grisette.Internal.TH.Derivation.DeriveSymEq
    ( deriveSymEq,
      deriveSymEq1,
      deriveSymEq2,
    )
  import Grisette.Internal.TH.Derivation.DeriveSymOrd
    ( deriveSymOrd,
      deriveSymOrd1,
      deriveSymOrd2,
    )
  import Grisette.Internal.TH.Derivation.DeriveToCon
    ( deriveToCon,
      deriveToCon1,
      deriveToCon2,
    )
  import Grisette.Internal.TH.Derivation.DeriveToSym
    ( deriveToSym,
      deriveToSym1,
      deriveToSym2,
    )
  import Grisette.Internal.TH.Derivation.DeriveUnifiedSimpleMergeable
    ( deriveUnifiedSimpleMergeable,
      deriveUnifiedSimpleMergeable1,
      deriveUnifiedSimpleMergeable2,
    )
  import Grisette.Internal.TH.Derivation.DeriveUnifiedSymEq
    ( deriveUnifiedSymEq,
      deriveUnifiedSymEq1,
      deriveUnifiedSymEq2,
    )
  import Grisette.Internal.TH.Derivation.DeriveUnifiedSymOrd
    ( deriveUnifiedSymOrd,
      deriveUnifiedSymOrd1,
      deriveUnifiedSymOrd2,
    )
  import Grisette.Internal.Unified.EvalModeTag (EvalModeTag (C, S))
  import Language.Haskell.TH (Dec, Name, Q)
  
  deriveProcedureMap :: M.Map Name (DeriveConfig -> Name -> Q [Dec])
  deriveProcedureMap =
    M.fromList
      [ (''EvalSym, deriveEvalSym),
        (''EvalSym1, deriveEvalSym1),
        (''EvalSym2, deriveEvalSym2),
        (''ExtractSym, deriveExtractSym),
        (''ExtractSym1, deriveExtractSym1),
        (''ExtractSym2, deriveExtractSym2),
        (''SubstSym, deriveSubstSym),
        (''SubstSym1, deriveSubstSym1),
        (''SubstSym2, deriveSubstSym2),
        (''NFData, deriveNFData),
        (''NFData1, deriveNFData1),
        (''NFData2, deriveNFData2),
        (''Hashable, deriveHashable),
        (''Hashable1, deriveHashable1),
        (''Hashable2, deriveHashable2),
        (''Show, deriveShow),
        (''Show1, deriveShow1),
        (''Show2, deriveShow2),
        (''PPrint, derivePPrint),
        (''PPrint1, derivePPrint1),
        (''PPrint2, derivePPrint2),
        (''AllSyms, deriveAllSyms),
        (''AllSyms1, deriveAllSyms1),
        (''AllSyms2, deriveAllSyms2),
        (''Eq, deriveEq),
        (''Eq1, deriveEq1),
        (''Eq2, deriveEq2),
        (''Ord, deriveOrd),
        (''Ord1, deriveOrd1),
        (''Ord2, deriveOrd2),
        (''SymOrd, deriveSymOrd),
        (''SymOrd1, deriveSymOrd1),
        (''SymOrd2, deriveSymOrd2),
        (''SymEq, deriveSymEq),
        (''SymEq1, deriveSymEq1),
        (''SymEq2, deriveSymEq2),
        (''UnifiedSymEq, deriveUnifiedSymEq),
        (''UnifiedSymEq1, deriveUnifiedSymEq1),
        (''UnifiedSymEq2, deriveUnifiedSymEq2),
        (''UnifiedSymOrd, deriveUnifiedSymOrd),
        (''UnifiedSymOrd1, deriveUnifiedSymOrd1),
        (''UnifiedSymOrd2, deriveUnifiedSymOrd2),
        (''ToSym, deriveToSym),
        (''ToSym1, deriveToSym1),
        (''ToSym2, deriveToSym2),
        (''ToCon, deriveToCon),
        (''ToCon1, deriveToCon1),
        (''ToCon2, deriveToCon2),
        (''Serial, deriveSerial),
        (''Serial1, deriveSerial1),
        (''Serial2, deriveSerial2),
        (''SimpleMergeable, deriveSimpleMergeable),
        (''SimpleMergeable1, deriveSimpleMergeable1),
        (''SimpleMergeable2, deriveSimpleMergeable2),
        (''UnifiedSimpleMergeable, deriveUnifiedSimpleMergeable),
        (''UnifiedSimpleMergeable1, deriveUnifiedSimpleMergeable1),
        (''UnifiedSimpleMergeable2, deriveUnifiedSimpleMergeable2),
        (''Binary, deriveBinary),
        (''Serialize, deriveCereal)
      ]
  
  deriveSingle :: DeriveConfig -> Name -> Name -> Q [Dec]
  deriveSingle deriveConfig typName className = do
    let newExtra
          | className
              `elem` [ ''Eq,
                       ''Eq1,
                       ''Eq2,
                       ''SymEq,
                       ''SymEq1,
                       ''SymEq2,
                       ''SymOrd,
                       ''SymOrd1,
                       ''SymOrd2,
                       ''UnifiedSymEq,
                       ''UnifiedSymEq1,
                       ''UnifiedSymEq2,
                       ''UnifiedSymOrd,
                       ''UnifiedSymOrd1,
                       ''UnifiedSymOrd2,
                       ''UnifiedSimpleMergeable,
                       ''UnifiedSimpleMergeable1,
                       ''UnifiedSimpleMergeable2
                     ] =
              deriveConfig
                { needExtraMergeableUnderEvalMode = False,
                  needExtraMergeableWithConcretizedEvalMode = False
                }
          | className
              `elem` [''SimpleMergeable, ''SimpleMergeable1, ''SimpleMergeable2] =
              deriveConfig
                { evalModeConfig =
                    second
                      ( \case
                          EvalModeConstraints _ -> EvalModeSpecified S
                          EvalModeSpecified tag -> EvalModeSpecified tag
                      )
                      <$> evalModeConfig deriveConfig,
                  needExtraMergeableUnderEvalMode = False,
                  needExtraMergeableWithConcretizedEvalMode = False
                }
          | className
              `elem` [''Ord, ''Ord1, ''Ord2, ''Hashable, ''Hashable1, ''Hashable2] =
              deriveConfig
                { evalModeConfig =
                    second
                      ( \case
                          EvalModeConstraints _ -> EvalModeSpecified C
                          EvalModeSpecified tag -> EvalModeSpecified tag
                      )
                      <$> evalModeConfig deriveConfig,
                  needExtraMergeableUnderEvalMode = False,
                  needExtraMergeableWithConcretizedEvalMode = False
                }
          | otherwise = deriveConfig
    case M.lookup className deriveProcedureMap of
      Just procedure -> procedure newExtra typName
      Nothing ->
        fail $ "No derivation available for class " ++ show className
  
  deriveWith' :: DeriveConfig -> Name -> [Name] -> Q [Dec]
  deriveWith' deriveConfig typName classNameList = do
    let classNames = S.fromList classNameList
    let (ns, ms) = splitMergeable $ S.toList classNames
    decs <- mapM (deriveSingle deriveConfig typName) ns
    decMergeables <- deriveMergeables ms
    return $ concat decs ++ decMergeables
    where
      configWithOutExtraMergeable :: DeriveConfig
      configWithOutExtraMergeable =
        deriveConfig {needExtraMergeableUnderEvalMode = False}
      deriveMergeables :: [Int] -> Q [Dec]
      deriveMergeables = genMergeableList configWithOutExtraMergeable typName
      splitMergeable :: [Name] -> ([Name], [Int])
      splitMergeable [] = ([], [])
      splitMergeable (x : xs) =
        let (ns, is) = splitMergeable xs
         in if
              | x == ''Mergeable -> (ns, 0 : is)
              | x == ''Mergeable1 -> (ns, 1 : is)
              | x == ''Mergeable2 -> (ns, 2 : is)
              | x == ''Mergeable3 -> (ns, 3 : is)
              | otherwise -> (x : ns, is)
  
  -- | Derive the specified classes for a data type with the given name.
  --
  -- Support the following classes for both vanilla data types and GADTs.
  --
  -- * 'Mergeable'
  -- * 'Mergeable1'
  -- * 'Mergeable2'
  -- * 'Mergeable3'
  -- * 'EvalSym'
  -- * 'EvalSym1'
  -- * 'EvalSym2'
  -- * 'ExtractSym'
  -- * 'ExtractSym1'
  -- * 'ExtractSym2'
  -- * 'SubstSym'
  -- * 'SubstSym1'
  -- * 'SubstSym2'
  -- * 'NFData'
  -- * 'NFData1'
  -- * 'NFData2'
  -- * 'Hashable' (will fail to derive if the type contains any symbolic variables)
  -- * 'Hashable1' (will fail to derive if the type contains any symbolic variables)
  -- * 'Hashable2' (will fail to derive if the type contains any symbolic variables)
  -- * 'Show'
  -- * 'Show1'
  -- * 'Show2'
  -- * 'PPrint'
  -- * 'PPrint1'
  -- * 'PPrint2'
  -- * 'AllSyms'
  -- * 'AllSyms1'
  -- * 'AllSyms2'
  -- * 'Eq'
  -- * 'Eq1'
  -- * 'Eq2'
  -- * 'Ord' (will fail to derive if the type contains any symbolic variables)
  -- * 'Ord1' (will fail to derive if the type contains any symbolic variables)
  -- * 'Ord2' (will fail to derive if the type contains any symbolic variables)
  -- * 'SymOrd'
  -- * 'SymOrd1'
  -- * 'SymOrd2'
  -- * 'SymEq'
  -- * 'SymEq1'
  -- * 'SymEq2'
  -- * 'UnifiedSymEq'
  -- * 'UnifiedSymEq1'
  -- * 'UnifiedSymEq2'
  -- * 'UnifiedSymOrd'
  -- * 'UnifiedSymOrd1'
  -- * 'UnifiedSymOrd2'
  -- * 'ToSym'
  -- * 'ToSym1'
  -- * 'ToSym2'
  -- * 'ToCon'
  -- * 'ToCon1'
  -- * 'ToCon2'
  -- * 'Serial'
  -- * 'Serial1'
  -- * 'Serial2'
  -- * 'SimpleMergeable'
  -- * 'SimpleMergeable1'
  -- * 'SimpleMergeable2'
  -- * 'Binary'
  -- * 'Serialize'
  --
  -- Note that the following type classes cannot be derived for GADTs with
  -- existential type variables.
  --
  -- * 'ToCon'
  -- * 'ToCon1'
  -- * 'ToCon2'
  -- * 'ToSym'
  -- * 'ToSym1'
  -- * 'ToSym2'
  -- * 'Serial'
  -- * 'Serial1'
  -- * 'Serial2'
  -- * 'Binary'
  -- * 'Serialize'
  deriveWith :: DeriveConfig -> [Name] -> [Name] -> Q [Dec]
  deriveWith deriveConfig typeNameList classNameList = do
    let typeNames = S.toList $ S.fromList typeNameList
    concat
      <$> traverse
        (\typeName -> deriveWith' deriveConfig typeName classNameList)
        typeNames
  
  -- | Derive the specified classes for a data type with the given name.
  --
  -- See 'deriveWith' for more details.
  derive :: [Name] -> [Name] -> Q [Dec]
  derive = deriveWith mempty
  
  -- | All the classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'Mergeable'
  -- * 'EvalSym'
  -- * 'ExtractSym'
  -- * 'SubstSym'
  -- * 'NFData'
  -- * 'Hashable' (will fail to derive if the type contains any symbolic variables)
  -- * 'Show'
  -- * 'PPrint'
  -- * 'AllSyms'
  -- * 'Eq'
  -- * 'SymEq'
  -- * 'SymOrd'
  -- * 'UnifiedSymEq'
  -- * 'Ord' (will fail to derive if the type contains any symbolic variables)
  -- * 'UnifiedSymOrd'
  -- * 'Serial'
  -- * 'ToCon'
  -- * 'ToSym'
  allClasses0 :: [Name]
  allClasses0 = basicClasses0 ++ concreteOrdClasses0 ++ noExistentialClasses0 ++ hashableClasses0
  
  -- | All the @*1@ classes that can be derived for GADT functors.
  --
  -- This includes:
  --
  -- * 'Mergeable1'
  -- * 'EvalSym1'
  -- * 'ExtractSym1'
  -- * 'SubstSym1'
  -- * 'NFData1'
  -- * 'Hashable1' (will fail to derive if the type contains any symbolic variables)
  -- * 'Show1'
  -- * 'PPrint1'
  -- * 'AllSyms1'
  -- * 'Eq1'
  -- * 'SymEq1'
  -- * 'SymOrd1'
  -- * 'UnifiedSymEq1'
  -- * 'Ord1' (will fail to derive if the type contains any symbolic variables)
  -- * 'UnifiedSymOrd1'
  -- * 'Serial1'
  -- * 'ToCon1'
  -- * 'ToSym1'
  allClasses1 :: [Name]
  allClasses1 = basicClasses1 ++ concreteOrdClasses1 ++ noExistentialClasses1 ++ hashableClasses1
  
  -- | All the classes that can be derived for GADT functors.
  --
  -- This includes all the classes in 'allClasses0' and 'allClasses1'.
  allClasses01 :: [Name]
  allClasses01 = allClasses0 ++ allClasses1
  
  -- | All the @*2@ classes that can be derived for GADT functors.
  --
  -- This includes:
  --
  -- * 'Mergeable2'
  -- * 'EvalSym2'
  -- * 'ExtractSym2'
  -- * 'SubstSym2'
  -- * 'NFData2'
  -- * 'Hashable2' (will fail to derive if the type contains any symbolic variables)
  -- * 'Show2'
  -- * 'PPrint2'
  -- * 'AllSyms2'
  -- * 'Eq2'
  -- * 'SymEq2'
  -- * 'SymOrd2'
  -- * 'UnifiedSymEq2'
  -- * 'Ord2' (will fail to derive if the type contains any symbolic variables)
  -- * 'UnifiedSymOrd2'
  -- * 'Serial2'
  -- * 'ToCon2'
  -- * 'ToSym2'
  allClasses2 :: [Name]
  allClasses2 = basicClasses2 ++ concreteOrdClasses2 ++ noExistentialClasses2 ++ hashableClasses2
  
  -- | All the classes that can be derived for GADT functors.
  --
  -- This includes all the classes in 'allClasses0', 'allClasses1',
  -- and 'allClasses2'.
  allClasses012 :: [Name]
  allClasses012 = allClasses0 ++ allClasses1 ++ allClasses2
  
  -- | Basic classes for GADTs.
  --
  -- This includes:
  --
  -- * 'Mergeable'
  -- * 'EvalSym'
  -- * 'ExtractSym'
  -- * 'SubstSym'
  -- * 'NFData'
  -- * 'Show'
  -- * 'PPrint'
  -- * 'AllSyms'
  -- * 'Eq'
  -- * 'SymEq'
  -- * 'SymOrd'
  -- * 'UnifiedSymEq'
  --
  -- These classes can be derived for most GADTs.
  basicClasses0 :: [Name]
  basicClasses0 =
    [ ''Mergeable,
      ''EvalSym,
      ''ExtractSym,
      ''SubstSym,
      ''NFData,
      ''Show,
      ''PPrint,
      ''AllSyms,
      ''Eq,
      ''SymEq,
      ''SymOrd,
      ''UnifiedSymEq
    ]
  
  -- | Classes that can only be derived for GADTs without existential type
  -- variables.
  --
  -- This includes:
  --
  -- * 'Serial'
  -- * 'Serialize'
  -- * 'Binary'
  -- * 'ToCon'
  -- * 'ToSym'
  noExistentialClasses0 :: [Name]
  noExistentialClasses0 = [''Serial, ''ToCon, ''ToSym, ''Serialize, ''Binary]
  
  -- | Concrete ordered classes that can be derived for GADTs that
  --
  -- * uses unified evaluation mode, or
  -- * does not contain any symbolic variables.
  --
  -- This includes:
  --
  -- * 'Ord' (will fail to derive if the type contains any symbolic variables)
  -- * 'UnifiedSymOrd'
  concreteOrdClasses0 :: [Name]
  concreteOrdClasses0 = [''Ord, ''UnifiedSymOrd]
  
  -- | Hashable classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'Hashable' (will fail to derive if the type contains any symbolic variables)
  hashableClasses0 :: [Name]
  hashableClasses0 = [''Hashable]
  
  -- | Basic classes for GADT functors.
  --
  -- This includes:
  --
  -- * 'Mergeable1'
  -- * 'EvalSym1'
  -- * 'ExtractSym1'
  -- * 'SubstSym1'
  -- * 'NFData1'
  -- * 'Show1'
  -- * 'PPrint1'
  -- * 'AllSyms1'
  -- * 'Eq1'
  -- * 'SymEq1'
  -- * 'SymOrd1'
  -- * 'UnifiedSymEq1'
  basicClasses1 :: [Name]
  basicClasses1 =
    [ ''Mergeable1,
      ''EvalSym1,
      ''ExtractSym1,
      ''SubstSym1,
      ''NFData1,
      ''Show1,
      ''PPrint1,
      ''AllSyms1,
      ''Eq1,
      ''SymEq1,
      ''SymOrd1,
      ''UnifiedSymEq1
    ]
  
  -- | Basic classes for GADT functors.
  --
  -- This includes all the classes in 'basicClasses0' and 'basicClasses1'.
  basicClasses01 :: [Name]
  basicClasses01 = basicClasses0 ++ basicClasses1
  
  -- | @*1@ classes that can only be derived for GADT functors without existential
  -- type variables.
  --
  -- This includes:
  --
  -- * 'Serial1'
  -- * 'ToCon1'
  -- * 'ToSym1'
  noExistentialClasses1 :: [Name]
  noExistentialClasses1 = [''Serial1, ''ToCon1, ''ToSym1]
  
  -- | @*1@ concrete ordered classes that can be derived for GADT functors that
  --
  -- * uses unified evaluation mode, or
  -- * does not contain any symbolic variables.
  --
  -- This includes:
  --
  -- * 'Ord1' (will fail to derive if the type contains any symbolic variables)
  -- * 'UnifiedSymOrd1'
  concreteOrdClasses1 :: [Name]
  concreteOrdClasses1 = [''Ord1, ''UnifiedSymOrd1]
  
  -- | Hashable classes that can be derived for GADT functors.
  --
  -- This includes:
  --
  -- * 'Hashable1' (will fail to derive if the type contains any symbolic variables)
  hashableClasses1 :: [Name]
  hashableClasses1 = [''Hashable1]
  
  -- | Basic classes for GADT functors.
  --
  -- This includes:
  --
  -- * 'Mergeable2'
  -- * 'EvalSym2'
  -- * 'ExtractSym2'
  -- * 'SubstSym2'
  -- * 'NFData2'
  -- * 'Show2'
  -- * 'PPrint2'
  -- * 'AllSyms2'
  -- * 'Eq2'
  -- * 'SymEq2'
  -- * 'SymOrd2'
  -- * 'UnifiedSymEq2'
  basicClasses2 :: [Name]
  basicClasses2 =
    [ ''Mergeable2,
      ''EvalSym2,
      ''ExtractSym2,
      ''SubstSym2,
      ''NFData2,
      ''Show2,
      ''PPrint2,
      ''AllSyms2,
      ''Eq2,
      ''SymEq2,
      ''SymOrd2,
      ''UnifiedSymEq2
    ]
  
  -- | Basic classes for GADT functors.
  --
  -- This includes all the classes in 'basicClasses0' and 'basicClasses1' and
  -- 'basicClasses2'.
  basicClasses012 :: [Name]
  basicClasses012 = basicClasses0 ++ basicClasses1 ++ basicClasses2
  
  -- | @*2@ classes that can only be derived for GADT functors without existential
  -- type variables.
  --
  -- This includes:
  --
  -- * 'Serial2'
  -- * 'ToCon2'
  -- * 'ToSym2'
  noExistentialClasses2 :: [Name]
  noExistentialClasses2 = [''Serial2, ''ToCon2, ''ToSym2]
  
  -- | @*2@ concrete ordered classes that can be derived for GADT functors that
  --
  -- * uses unified evaluation mode, or
  -- * does not contain any symbolic variables.
  --
  -- This includes:
  --
  -- * 'Ord2' (will fail to derive if the type contains any symbolic variables)
  -- * 'UnifiedSymOrd2'
  concreteOrdClasses2 :: [Name]
  concreteOrdClasses2 = [''Ord2, ''UnifiedSymOrd2]
  
  -- | Hashable classes that can be derived for GADT functors.
  --
  -- This includes:
  --
  -- * 'Hashable2' (will fail to derive if the type contains any symbolic variables)
  hashableClasses2 :: [Name]
  hashableClasses2 = [''Hashable2]
  
  -- | 'Show' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'Show'
  -- * 'Show1'
  -- * 'Show2'
  showClasses :: [Name]
  showClasses = [''Show, ''Show1, ''Show2]
  
  -- | 'PPrint' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'PPrint'
  -- * 'PPrint1'
  -- * 'PPrint2'
  pprintClasses :: [Name]
  pprintClasses = [''PPrint, ''PPrint1, ''PPrint2]
  
  -- | 'EvalSym' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'EvalSym'
  -- * 'EvalSym1'
  -- * 'EvalSym2'
  evalSymClasses :: [Name]
  evalSymClasses = [''EvalSym, ''EvalSym1, ''EvalSym2]
  
  -- | 'ExtractSym' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'ExtractSym'
  -- * 'ExtractSym1'
  -- * 'ExtractSym2'
  extractSymClasses :: [Name]
  extractSymClasses = [''ExtractSym, ''ExtractSym1, ''ExtractSym2]
  
  -- | 'SubstSym' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'SubstSym'
  -- * 'SubstSym1'
  -- * 'SubstSym2'
  substSymClasses :: [Name]
  substSymClasses = [''SubstSym, ''SubstSym1, ''SubstSym2]
  
  -- | 'AllSyms' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'AllSyms'
  -- * 'AllSyms1'
  -- * 'AllSyms2'
  allSymsClasses :: [Name]
  allSymsClasses = [''AllSyms, ''AllSyms1, ''AllSyms2]
  
  -- | 'Eq' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'Eq'
  -- * 'Eq1'
  -- * 'Eq2'
  eqClasses :: [Name]
  eqClasses = [''Eq, ''Eq1, ''Eq2]
  
  -- | 'SymEq' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'SymEq'
  -- * 'SymEq1'
  -- * 'SymEq2'
  symEqClasses :: [Name]
  symEqClasses = [''SymEq, ''SymEq1, ''SymEq2]
  
  -- | 'UnifiedSymEq' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'UnifiedSymEq'
  -- * 'UnifiedSymEq1'
  -- * 'UnifiedSymEq2'
  unifiedSymEqClasses :: [Name]
  unifiedSymEqClasses = [''UnifiedSymEq, ''UnifiedSymEq1, ''UnifiedSymEq2]
  
  -- | 'Ord' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'Ord' (will fail to derive if the type contains any symbolic variables)
  -- * 'Ord1' (will fail to derive if the type contains any symbolic variables)
  -- * 'Ord2' (will fail to derive if the type contains any symbolic variables)
  ordClasses :: [Name]
  ordClasses = [''Ord, ''Ord1, ''Ord2]
  
  -- | 'SymOrd' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'SymOrd'
  -- * 'SymOrd1'
  -- * 'SymOrd2'
  symOrdClasses :: [Name]
  symOrdClasses = [''SymOrd, ''SymOrd1, ''SymOrd2]
  
  -- | 'UnifiedSymOrd' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'UnifiedSymOrd'
  -- * 'UnifiedSymOrd1'
  -- * 'UnifiedSymOrd2'
  unifiedSymOrdClasses :: [Name]
  unifiedSymOrdClasses = [''UnifiedSymOrd, ''UnifiedSymOrd1, ''UnifiedSymOrd2]
  
  -- | 'Mergeable' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'Mergeable'
  -- * 'Mergeable1'
  -- * 'Mergeable2'
  -- * 'Mergeable3'
  mergeableClasses :: [Name]
  mergeableClasses = [''Mergeable, ''Mergeable1, ''Mergeable2, ''Mergeable3]
  
  -- | 'NFData' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'NFData'
  -- * 'NFData1'
  -- * 'NFData2'
  nfDataClasses :: [Name]
  nfDataClasses = [''NFData, ''NFData1, ''NFData2]
  
  -- | 'Hashable' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'Hashable' (will fail to derive if the type contains any symbolic variables)
  -- * 'Hashable1' (will fail to derive if the type contains any symbolic variables)
  -- * 'Hashable2' (will fail to derive if the type contains any symbolic variables)
  hashableClasses :: [Name]
  hashableClasses = [''Hashable, ''Hashable1, ''Hashable2]
  
  -- | 'ToSym' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'ToSym'
  -- * 'ToSym1'
  -- * 'ToSym2'
  toSymClasses :: [Name]
  toSymClasses = [''ToSym, ''ToSym1, ''ToSym2]
  
  -- | 'ToCon' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'ToCon'
  -- * 'ToCon1'
  -- * 'ToCon2'
  toConClasses :: [Name]
  toConClasses = [''ToCon, ''ToCon1, ''ToCon2]
  
  -- | 'Serial' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'Serial'
  -- * 'Serial1'
  -- * 'Serial2'
  serialClasses :: [Name]
  serialClasses = [''Serial, ''Serial1, ''Serial2]
  
  -- | 'SimpleMergeable' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'SimpleMergeable'
  -- * 'SimpleMergeable1'
  -- * 'SimpleMergeable2'
  simpleMergeableClasses :: [Name]
  simpleMergeableClasses =
    [''SimpleMergeable, ''SimpleMergeable1, ''SimpleMergeable2]
  
  -- | 'UnifiedSimpleMergeable' classes that can be derived for GADTs.
  --
  -- This includes:
  --
  -- * 'UnifiedSimpleMergeable'
  -- * 'UnifiedSimpleMergeable1'
  -- * 'UnifiedSimpleMergeable2'
  unifiedSimpleMergeableClasses :: [Name]
  unifiedSimpleMergeableClasses =
    [ ''UnifiedSimpleMergeable,
      ''UnifiedSimpleMergeable1,
      ''UnifiedSimpleMergeable2
    ]
  
  clsArgNumArgs :: Name -> Int
  clsArgNumArgs cls =
    if
      | cls `elem` allClasses0 -> 0
      | cls `elem` allClasses1 -> 1
      | cls `elem` allClasses2 -> 2
      | cls == ''Mergeable3 -> 3
      | otherwise -> error $ "clsArgNumArgs: unknown class: " ++ show cls
  
  -- | Filter classes that accepts type constructors with exactly @n@ arguments.
  filterExactNumArgs :: Int -> [Name] -> [Name]
  filterExactNumArgs n = filter (\cls -> clsArgNumArgs cls == n)
  
  -- | Filter classes that accepts type constructors with at most @n@ arguments.
  filterLeqNumArgs :: Int -> [Name] -> [Name]
  filterLeqNumArgs n = filter (\cls -> clsArgNumArgs cls <= n)
  