-- Copyright (c) Facebook, Inc. and its affiliates.
--
-- This source code is licensed under the MIT license found in the
-- LICENSE file in the root directory of this source tree.
--
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}
module Retrie.PatternMap.Instances where

import Control.Monad
import Data.ByteString (ByteString)
import Data.Maybe

import Retrie.AlphaEnv
import Retrie.ExactPrint
import Retrie.GHC
import Retrie.PatternMap.Bag
import Retrie.PatternMap.Class
import Retrie.Quantifiers
import Retrie.Substitution

------------------------------------------------------------------------

data TupArgMap a
  = TupArgMap { tamPresent :: EMap a, tamMissing :: MaybeMap a }
  deriving (Functor)

instance PatternMap TupArgMap where
  type Key TupArgMap = LHsTupArg GhcPs

  mEmpty :: TupArgMap a
  mEmpty = TupArgMap mEmpty mEmpty

  mUnion :: TupArgMap a -> TupArgMap a -> TupArgMap a
  mUnion m1 m2 = TupArgMap
    { tamPresent = unionOn tamPresent m1 m2
    , tamMissing = unionOn tamMissing m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key TupArgMap -> A a -> TupArgMap a -> TupArgMap a
  mAlter env vs tupArg f m = go (unLoc tupArg)
    where
#if __GLASGOW_HASKELL__ < 806
      go (Present e) = m { tamPresent = mAlter env vs e  f (tamPresent m) }
#else
      go (Present _ e) = m { tamPresent = mAlter env vs e  f (tamPresent m) }
      go XTupArg{} = missingSyntax "XTupArg"
#endif
      go (Missing _) = m { tamMissing = mAlter env vs () f (tamMissing m) }

  mMatch :: MatchEnv -> Key TupArgMap -> (Substitution, TupArgMap a) -> [(Substitution, a)]
  mMatch env = go . unLoc
    where
#if __GLASGOW_HASKELL__ < 806
      go (Present e) = mapFor tamPresent >=> mMatch env e
#else
      go (Present _ e) = mapFor tamPresent >=> mMatch env e
      go XTupArg{} = const []
#endif
      go (Missing _) = mapFor tamMissing >=> mMatch env ()

------------------------------------------------------------------------

data BoxityMap a
  = BoxityMap { boxBoxed :: MaybeMap a, boxUnboxed :: MaybeMap a }
  deriving (Functor)

instance PatternMap BoxityMap where
  type Key BoxityMap = Boxity

  mEmpty :: BoxityMap a
  mEmpty = BoxityMap mEmpty mEmpty

  mUnion :: BoxityMap a -> BoxityMap a -> BoxityMap a
  mUnion m1 m2 = BoxityMap
    { boxBoxed = unionOn boxBoxed m1 m2
    , boxUnboxed = unionOn boxUnboxed m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key BoxityMap -> A a -> BoxityMap a -> BoxityMap a
  mAlter env vs Boxed   f m = m { boxBoxed   = mAlter env vs () f (boxBoxed m) }
  mAlter env vs Unboxed f m = m { boxUnboxed = mAlter env vs () f (boxUnboxed m) }

  mMatch :: MatchEnv -> Key BoxityMap -> (Substitution, BoxityMap a) -> [(Substitution, a)]
  mMatch env Boxed   = mapFor boxBoxed >=> mMatch env ()
  mMatch env Unboxed = mapFor boxUnboxed >=> mMatch env ()

------------------------------------------------------------------------

data VMap a = VM { bvmap :: IntMap a, fvmap :: FSEnv a }
            | VMEmpty
  deriving (Functor)

instance PatternMap VMap where
  type Key VMap = RdrName

  mEmpty :: VMap a
  mEmpty = VMEmpty

  mUnion :: VMap a -> VMap a -> VMap a
  mUnion VMEmpty m = m
  mUnion m VMEmpty = m
  mUnion m1 m2 = VM
    { bvmap = unionOn bvmap m1 m2
    , fvmap = unionOn fvmap m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key VMap -> A a -> VMap a -> VMap a
  mAlter env vs v f VMEmpty = mAlter env vs v f (VM mEmpty mEmpty)
  mAlter env vs v f m@VM{}
    | Just bv <- lookupAlphaEnv v env = m { bvmap = mAlter env vs bv f (bvmap m) }
    | otherwise                       = m { fvmap = mAlter env vs (rdrFS v) f (fvmap m) }

  mMatch :: MatchEnv -> Key VMap -> (Substitution, VMap a) -> [(Substitution, a)]
  mMatch _   _ (_,VMEmpty) = []
  mMatch env v (hs,m@VM{})
    | Just bv <- lookupAlphaEnv v (meAlphaEnv env) = mMatch env bv (hs, bvmap m)
    | otherwise = mMatch env (rdrFS v) (hs, fvmap m)

------------------------------------------------------------------------

data LMap a
  = LMEmpty
  | LM { lmChar :: Map Char a
       , lmCharPrim :: Map Char a
       , lmString :: FSEnv a
       , lmStringPrim :: Map ByteString a
       , lmInt :: BoolMap (Map Integer a)
       , lmIntPrim :: Map Integer a
       , lmWordPrim :: Map Integer a
       , lmInt64Prim :: Map Integer a
       , lmWord64Prim :: Map Integer a
       }
  deriving (Functor)

emptyLMapWrapper :: LMap a
emptyLMapWrapper
  = LM mEmpty mEmpty mEmpty mEmpty mEmpty
       mEmpty mEmpty mEmpty mEmpty

instance PatternMap LMap where
  type Key LMap = HsLit GhcPs

  mEmpty :: LMap a
  mEmpty = LMEmpty

  mUnion :: LMap a -> LMap a -> LMap a
  mUnion LMEmpty m = m
  mUnion m LMEmpty = m
  mUnion m1 m2 = LM
    { lmChar = unionOn lmChar m1 m2
    , lmCharPrim = unionOn lmCharPrim m1 m2
    , lmString = unionOn lmString m1 m2
    , lmStringPrim = unionOn lmStringPrim m1 m2
    , lmInt = unionOn lmInt m1 m2
    , lmIntPrim = unionOn lmIntPrim m1 m2
    , lmWordPrim = unionOn lmWordPrim m1 m2
    , lmInt64Prim = unionOn lmInt64Prim m1 m2
    , lmWord64Prim = unionOn lmWord64Prim m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key LMap -> A a -> LMap a -> LMap a
  mAlter env vs lit f LMEmpty = mAlter env vs lit f emptyLMapWrapper
  mAlter env vs lit f m@LM{}  = go lit
    where
      go (HsChar _ c)       = m { lmChar = mAlter env vs c f (lmChar m) }
      go (HsCharPrim _ c)   = m { lmCharPrim = mAlter env vs c f (lmCharPrim m) }
      go (HsString _ fs)    = m { lmString = mAlter env vs fs f (lmString m) }
      go (HsStringPrim _ bs) = m { lmStringPrim = mAlter env vs bs f (lmStringPrim m) }
      go (HsInt _ (IL _ b i)) =
        m { lmInt = mAlter env vs b (toA (mAlter env vs i f)) (lmInt m) }
      go (HsIntPrim _ i)    = m { lmIntPrim = mAlter env vs i f (lmIntPrim m) }
      go (HsWordPrim _ i)   = m { lmWordPrim = mAlter env vs i f (lmWordPrim m) }
      go (HsInt64Prim _ i)  = m { lmInt64Prim = mAlter env vs i f (lmInt64Prim m) }
      go (HsWord64Prim _ i) = m { lmWord64Prim = mAlter env vs i f (lmWord64Prim m) }
      go (HsInteger _ _ _) = missingSyntax "HsInteger"
      go HsRat{} = missingSyntax "HsRat"
      go HsFloatPrim{} = missingSyntax "HsFloatPrim"
      go HsDoublePrim{} = missingSyntax "HsDoublePrim"
#if __GLASGOW_HASKELL__ < 806
#else
      go XLit{} = missingSyntax "XLit"
#endif

  mMatch :: MatchEnv -> Key LMap -> (Substitution, LMap a) -> [(Substitution, a)]
  mMatch _   _   (_,LMEmpty) = []
  mMatch env lit (hs,m@LM{}) = go lit (hs,m)
    where
      go (HsChar _ c)        = mapFor lmChar >=> mMatch env c
      go (HsCharPrim _ c)    = mapFor lmCharPrim >=> mMatch env c
      go (HsString _ fs)     = mapFor lmString >=> mMatch env fs
      go (HsStringPrim _ bs) = mapFor lmStringPrim >=> mMatch env bs
      go (HsInt _ (IL _ b i)) = mapFor lmInt >=> mMatch env b >=> mMatch env i
      go (HsIntPrim _ i)     = mapFor lmIntPrim >=> mMatch env i
      go (HsWordPrim _ i)    = mapFor lmWordPrim >=> mMatch env i
      go (HsInt64Prim _ i)   = mapFor lmInt64Prim >=> mMatch env i
      go (HsWord64Prim _ i)  = mapFor lmWord64Prim >=> mMatch env i
      go _ = const [] -- TODO

------------------------------------------------------------------------

data OLMap a
  = OLMEmpty
  | OLM
    { olmIntegral :: BoolMap (Map Integer a)
    , olmFractional :: Map Rational a
    , olmIsString :: FSEnv a
    }
  deriving (Functor)

emptyOLMapWrapper :: OLMap a
emptyOLMapWrapper = OLM mEmpty mEmpty mEmpty

instance PatternMap OLMap where
  type Key OLMap = OverLitVal

  mEmpty :: OLMap a
  mEmpty = OLMEmpty

  mUnion :: OLMap a -> OLMap a -> OLMap a
  mUnion OLMEmpty m = m
  mUnion m OLMEmpty = m
  mUnion m1 m2 = OLM
    { olmIntegral = unionOn olmIntegral m1 m2
    , olmFractional = unionOn olmFractional m1 m2
    , olmIsString = unionOn olmIsString m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key OLMap -> A a -> OLMap a -> OLMap a
  mAlter env vs lv f OLMEmpty = mAlter env vs lv f emptyOLMapWrapper
  mAlter env vs lv f m@OLM{}  = go lv
    where
      go (HsIntegral (IL _ b i)) =
        m { olmIntegral = mAlter env vs b (toA (mAlter env vs i f)) (olmIntegral m) }
      go (HsFractional fl) = m { olmFractional = mAlter env vs (fl_value fl) f (olmFractional m) }
      go (HsIsString _ fs) = m { olmIsString = mAlter env vs fs f (olmIsString m) }

  mMatch :: MatchEnv -> Key OLMap -> (Substitution, OLMap a) -> [(Substitution, a)]
  mMatch _   _  (_,OLMEmpty) = []
  mMatch env lv (hs,m@OLM{}) = go lv (hs,m)
    where
      go (HsIntegral (IL _ b i)) =
        mapFor olmIntegral >=> mMatch env b >=> mMatch env i
      go (HsFractional fl) = mapFor olmFractional >=> mMatch env (fl_value fl)
      go (HsIsString _ fs) = mapFor olmIsString >=> mMatch env fs

------------------------------------------------------------------------

-- Note [Holes]
-- Holes are distinguished variables which can match any expression. (The
-- universally quantified variables in an Equality.) Ideally, they would be
-- stored as a TyMap, so the type of the expression can be checked against the
-- type of the hole. Fixing this is a TODO. This wraps a map from RdrName to
-- result. We use a regular map instead of a OccEnv so we can get the RdrName
-- back, which allows us to assign it to the expression when building the
-- result.

-- Note [Lambdas]
-- This currently stores both HsLam and HsLamCase

-- Note [Stmt Lists]
-- Statement lists bind to the right, so we need to extend the environment
-- as we move down it. Thus we cannot simply store them as ListMap SMap a.

-- Note [Dollar Fork]
-- When 'f $ x' appears in the pattern, we insert two things in the EMap
-- instead of just one:
--
-- * The original infix application of ($).
-- * The expression transformed into a normal application with parens around
--   the right argument to ($). i.e. f (x)
--
-- This allows us to put ($) in the LHS of rewrites and match both literal ($)
-- applications and the parenthesized equivalent.

data EMap a
  = EMEmpty
  | EM { emHole  :: Map RdrName a -- See Note [Holes]
       , emVar   :: VMap a
       , emIPVar :: FSEnv a
       , emOverLit :: OLMap a
       , emLit   :: LMap a
       , emLam   :: MGMap a -- See Note [Lambdas]
       , emApp   :: EMap (EMap a)
       , emOpApp :: EMap (EMap (EMap a)) -- op, lhs, rhs
       , emNegApp :: EMap a
       , emPar   :: EMap a
       , emExplicitTuple :: BoxityMap (ListMap TupArgMap a)
       , emCase  :: EMap (MGMap a)
       , emSecL  :: EMap (EMap a) -- operator, operand (flipped)
       , emSecR  :: EMap (EMap a) -- operator, operand
       , emIf    :: EMap (EMap (EMap a)) -- cond, true, false
       , emLet   :: LBMap (EMap a)
       , emDo    :: SCMap (SLMap a) -- See Note [Stmt Lists]
       , emExplicitList :: ListMap EMap a
       , emRecordCon :: VMap (ListMap RFMap a)
       , emRecordUpd :: EMap (ListMap RFMap a)
       , emExprWithTySig :: EMap (TyMap a)
       }
  deriving (Functor)

emptyEMapWrapper :: EMap a
emptyEMapWrapper =
  EM mEmpty mEmpty mEmpty mEmpty mEmpty
     mEmpty mEmpty mEmpty mEmpty mEmpty
     mEmpty mEmpty mEmpty mEmpty mEmpty
     mEmpty mEmpty mEmpty mEmpty mEmpty
     mEmpty

instance PatternMap EMap where
  type Key EMap = LHsExpr GhcPs

  mEmpty :: EMap a
  mEmpty = EMEmpty

  mUnion :: EMap a -> EMap a -> EMap a
  mUnion EMEmpty m = m
  mUnion m EMEmpty = m
  mUnion m1 m2 = EM
    { emHole = unionOn emHole m1 m2
    , emVar = unionOn emVar m1 m2
    , emIPVar = unionOn emIPVar m1 m2
    , emOverLit = unionOn emOverLit m1 m2
    , emLit = unionOn emLit m1 m2
    , emLam = unionOn emLam m1 m2
    , emApp = unionOn emApp m1 m2
    , emOpApp = unionOn emOpApp m1 m2
    , emNegApp = unionOn emNegApp m1 m2
    , emPar = unionOn emPar m1 m2
    , emExplicitTuple = unionOn emExplicitTuple m1 m2
    , emCase = unionOn emCase m1 m2
    , emSecL = unionOn emSecL m1 m2
    , emSecR = unionOn emSecR m1 m2
    , emIf = unionOn emIf m1 m2
    , emLet = unionOn emLet m1 m2
    , emDo = unionOn emDo m1 m2
    , emExplicitList = unionOn emExplicitList m1 m2
    , emRecordCon = unionOn emRecordCon m1 m2
    , emRecordUpd = unionOn emRecordUpd m1 m2
    , emExprWithTySig = unionOn emExprWithTySig m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key EMap -> A a -> EMap a -> EMap a
  mAlter env vs e f EMEmpty = mAlter env vs e f emptyEMapWrapper
  mAlter env vs e f m@EM{} = go (unLoc e)
    where
      -- See Note [Dollar Fork]
      dollarFork v@HsVar{} l r
        | Just (L _ rdr) <- varRdrName v
        , occNameString (occName rdr) == "$" =
#if __GLASGOW_HASKELL__ < 806
          go (HsApp l (noLoc (HsPar r)))
#else
          go (HsApp noExtField l (noLoc (HsPar noExtField r)))
#endif
      dollarFork _ _ _ = m

#if __GLASGOW_HASKELL__ < 806
      go (HsVar v)
        | unLoc v `isQ` vs = m { emHole  = mAlter env vs (unLoc v) f (emHole m) }
        | otherwise        = m { emVar   = mAlter env vs (unLoc v) f (emVar m) }
      go (ExplicitTuple as b) =
        m { emExplicitTuple = mAlter env vs b (toA (mAlter env vs as f)) (emExplicitTuple m) }
      go (HsApp l r) =
        m { emApp = mAlter env vs l (toA (mAlter env vs r f)) (emApp m) }
      go (HsCase s mg) =
        m { emCase = mAlter env vs s (toA (mAlter env vs mg f)) (emCase m) }
      go (HsDo sc ss _) =
        m { emDo = mAlter env vs sc (toA (mAlter env vs (unLoc ss) f)) (emDo m) }
      go (HsIf _ c tr fl) =
        m { emIf = mAlter env vs c
                      (toA (mAlter env vs tr
                          (toA (mAlter env vs fl f)))) (emIf m) }
      go (HsIPVar (HsIPName ip)) = m { emIPVar = mAlter env vs ip f (emIPVar m) }
      go (HsLit l) = m { emLit   = mAlter env vs l f (emLit m) }
      go (HsLam mg) = m { emLam   = mAlter env vs mg f (emLam m) }
      go (HsOverLit ol) = m { emOverLit = mAlter env vs (ol_val ol) f (emOverLit m) }
      go (NegApp e' _) = m { emNegApp = mAlter env vs e' f (emNegApp m) }
      go (HsPar e') = m { emPar  = mAlter env vs e' f (emPar m) }
      go (OpApp l o _ r) = (dollarFork (unLoc o) l r)
        { emOpApp = mAlter env vs o (toA (mAlter env vs l (toA (mAlter env vs r f)))) (emOpApp m) }
      go (RecordCon v _ _ fs) =
        m { emRecordCon = mAlter env vs (unLoc v) (toA (mAlter env vs (fieldsToRdrNames $ rec_flds fs) f)) (emRecordCon m) }
      go (RecordUpd e' fs _ _ _ _) =
        m { emRecordUpd = mAlter env vs e' (toA (mAlter env vs (fieldsToRdrNames fs) f)) (emRecordUpd m) }
      go (SectionL lhs o) =
        m { emSecL = mAlter env vs o (toA (mAlter env vs lhs f)) (emSecL m) }
      go (SectionR o rhs) =
        m { emSecR = mAlter env vs o (toA (mAlter env vs rhs f)) (emSecR m) }
      go (HsLet lbs e') =
#else
      go (HsVar _ v)
        | unLoc v `isQ` vs = m { emHole  = mAlter env vs (unLoc v) f (emHole m) }
        | otherwise        = m { emVar   = mAlter env vs (unLoc v) f (emVar m) }
      go (ExplicitTuple _ as b) =
        m { emExplicitTuple = mAlter env vs b (toA (mAlter env vs as f)) (emExplicitTuple m) }
      go (HsApp _ l r) =
        m { emApp = mAlter env vs l (toA (mAlter env vs r f)) (emApp m) }
      go (HsCase _ s mg) =
        m { emCase = mAlter env vs s (toA (mAlter env vs mg f)) (emCase m) }
      go (HsDo _ sc ss) =
        m { emDo = mAlter env vs sc (toA (mAlter env vs (unLoc ss) f)) (emDo m) }
      go (HsIf _ _ c tr fl) =
        m { emIf = mAlter env vs c
                      (toA (mAlter env vs tr
                          (toA (mAlter env vs fl f)))) (emIf m) }
      go (HsIPVar _ (HsIPName ip)) = m { emIPVar = mAlter env vs ip f (emIPVar m) }
      go (HsLit _ l) = m { emLit   = mAlter env vs l f (emLit m) }
      go (HsLam _ mg) = m { emLam   = mAlter env vs mg f (emLam m) }
      go (HsOverLit _ ol) = m { emOverLit = mAlter env vs (ol_val ol) f (emOverLit m) }
      go (NegApp _ e' _) = m { emNegApp = mAlter env vs e' f (emNegApp m) }
      go (HsPar _ e') = m { emPar  = mAlter env vs e' f (emPar m) }
      go (OpApp _ l o r) = (dollarFork (unLoc o) l r)
        { emOpApp = mAlter env vs o (toA (mAlter env vs l (toA (mAlter env vs r f)))) (emOpApp m) }
      go (RecordCon _ v fs) =
        m { emRecordCon = mAlter env vs (unLoc v) (toA (mAlter env vs (fieldsToRdrNames $ rec_flds fs) f)) (emRecordCon m) }
      go (RecordUpd _ e' fs) =
        m { emRecordUpd = mAlter env vs e' (toA (mAlter env vs (fieldsToRdrNames fs) f)) (emRecordUpd m) }
      go (SectionL _ lhs o) =
        m { emSecL = mAlter env vs o (toA (mAlter env vs lhs f)) (emSecL m) }
      go (SectionR _ o rhs) =
        m { emSecR = mAlter env vs o (toA (mAlter env vs rhs f)) (emSecR m) }
      go XExpr{} = missingSyntax "XExpr"
      go (HsLet _ lbs e') =
#endif
        let
          bs = collectLocalBinders $ unLoc lbs
          env' = foldr extendAlphaEnvInternal env bs
          vs' = vs `exceptQ` bs
        in m { emLet = mAlter env vs (unLoc lbs) (toA (mAlter env' vs' e' f)) (emLet m) }
      go HsLamCase{} = missingSyntax "HsLamCase"
      go HsMultiIf{} = missingSyntax "HsMultiIf"
      go (ExplicitList _ _ es) = m { emExplicitList = mAlter env vs es f (emExplicitList m) }
      go ArithSeq{} = missingSyntax "ArithSeq"
#if __GLASGOW_HASKELL__ < 806
      go (ExprWithTySig e' (HsWC _ (HsIB _ ty _))) =
        m { emExprWithTySig = mAlter env vs e' (toA (mAlter env vs ty f)) (emExprWithTySig m) }
#else
#if __GLASGOW_HASKELL__ < 808
      go (ExprWithTySig (HsWC _ (HsIB _ ty)) e') =
#else
      go (ExprWithTySig _ e' (HsWC _ (HsIB _ ty))) =
#endif
        m { emExprWithTySig = mAlter env vs e' (toA (mAlter env vs ty f)) (emExprWithTySig m) }
      go ExprWithTySig{} = missingSyntax "ExprWithTySig"
#endif
      go HsSCC{} = missingSyntax "HsSCC"
      go HsCoreAnn{} = missingSyntax "HsCoreAnn"
      go HsBracket{} = missingSyntax "HsBracket"
      go HsRnBracketOut{} = missingSyntax "HsRnBracketOut"
      go HsTcBracketOut{} = missingSyntax "HsTcBracketOut"
      go HsSpliceE{} = missingSyntax "HsSpliceE"
      go HsProc{} = missingSyntax "HsProc"
      go HsStatic{} = missingSyntax "HsStatic"
#if __GLASGOW_HASKELL__ < 810
      go HsArrApp{} = missingSyntax "HsArrApp"
      go HsArrForm{} = missingSyntax "HsArrForm"
      go EWildPat{} = missingSyntax "EWildPat"
      go EAsPat{} = missingSyntax "EAsPat"
      go EViewPat{} = missingSyntax "EViewPat"
      go ELazyPat{} = missingSyntax "ELazyPat"
#endif
      go HsTick{} = missingSyntax "HsTick"
      go HsBinTick{} = missingSyntax "HsBinTick"
      go HsTickPragma{} = missingSyntax "HsTickPragma"
      go HsWrap{} = missingSyntax "HsWrap"
      go HsUnboundVar{} = missingSyntax "HsUnboundVar"
      go HsRecFld{} = missingSyntax "HsRecFld"
      go HsOverLabel{} = missingSyntax "HsOverLabel"
      go HsAppType{} = missingSyntax "HsAppType"
      go HsConLikeOut{} = missingSyntax "HsConLikeOut"
      go ExplicitSum{} = missingSyntax "ExplicitSum"
#if __GLASGOW_HASKELL__ < 806
      go ExplicitPArr{} = missingSyntax "ExplicitPArr"
      go ExprWithTySigOut{} = missingSyntax "ExprWithTySigOut"
      go HsAppTypeOut{} = missingSyntax "HsAppTypeOut"
      go PArrSeq{} = missingSyntax "PArrSeq"
#endif

  mMatch :: MatchEnv -> Key EMap -> (Substitution, EMap a) -> [(Substitution, a)]
  mMatch _   _ (_,EMEmpty) = []
  mMatch env e (hs,m@EM{}) = hss ++ go (unLoc e) (hs,m)
    where
      hss = extendResult (emHole m) (HoleExpr $ mePruneA env e) hs

#if __GLASGOW_HASKELL__ < 806
      go (ExplicitTuple as b) = mapFor emExplicitTuple >=> mMatch env b >=> mMatch env as
      go (HsApp l r) = mapFor emApp >=> mMatch env l >=> mMatch env r
      go (HsCase s mg) = mapFor emCase >=> mMatch env s >=> mMatch env mg
      go (HsDo sc ss _) = mapFor emDo >=> mMatch env sc >=> mMatch env (unLoc ss)
      go (HsIf _ c tr fl) =
        mapFor emIf >=> mMatch env c >=> mMatch env tr >=> mMatch env fl
      go (HsIPVar (HsIPName ip)) = mapFor emIPVar >=> mMatch env ip
      go (HsLam mg) = mapFor emLam >=> mMatch env mg
      go (HsLit l) = mapFor emLit >=> mMatch env l
      go (HsOverLit ol) = mapFor emOverLit >=> mMatch env (ol_val ol)
      go (HsPar e') = mapFor emPar >=> mMatch env e'
      go (HsVar v) = mapFor emVar >=> mMatch env (unLoc v)
      go (NegApp e' _) = mapFor emNegApp >=> mMatch env e'
      go (OpApp l o _ r) =
        mapFor emOpApp >=> mMatch env o >=> mMatch env l >=> mMatch env r
      go (RecordCon v _ _ fs) =
        mapFor emRecordCon >=> mMatch env (unLoc v) >=> mMatch env (fieldsToRdrNames $ rec_flds fs)
      go (RecordUpd e' fs _ _ _ _) =
        mapFor emRecordUpd >=> mMatch env e' >=> mMatch env (fieldsToRdrNames fs)
      go (SectionL lhs o) = mapFor emSecL >=> mMatch env o >=> mMatch env lhs
      go (SectionR o rhs) = mapFor emSecR >=> mMatch env o >=> mMatch env rhs
      go (HsLet lbs e') =
#else
      go (ExplicitTuple _ as b) = mapFor emExplicitTuple >=> mMatch env b >=> mMatch env as
      go (HsApp _ l r) = mapFor emApp >=> mMatch env l >=> mMatch env r
      go (HsCase _ s mg) = mapFor emCase >=> mMatch env s >=> mMatch env mg
      go (HsDo _ sc ss) = mapFor emDo >=> mMatch env sc >=> mMatch env (unLoc ss)
      go (HsIf _ _ c tr fl) =
        mapFor emIf >=> mMatch env c >=> mMatch env tr >=> mMatch env fl
      go (HsIPVar _ (HsIPName ip)) = mapFor emIPVar >=> mMatch env ip
      go (HsLam _ mg) = mapFor emLam >=> mMatch env mg
      go (HsLit _ l) = mapFor emLit >=> mMatch env l
      go (HsOverLit _ ol) = mapFor emOverLit >=> mMatch env (ol_val ol)
      go (HsPar _ e') = mapFor emPar >=> mMatch env e'
      go (HsVar _ v) = mapFor emVar >=> mMatch env (unLoc v)
      go (OpApp _ l o r) =
        mapFor emOpApp >=> mMatch env o >=> mMatch env l >=> mMatch env r
      go (NegApp _ e' _) = mapFor emNegApp >=> mMatch env e'
      go (RecordCon _ v fs) =
        mapFor emRecordCon >=> mMatch env (unLoc v) >=> mMatch env (fieldsToRdrNames $ rec_flds fs)
      go (RecordUpd _ e' fs) =
        mapFor emRecordUpd >=> mMatch env e' >=> mMatch env (fieldsToRdrNames fs)
      go (SectionL _ lhs o) = mapFor emSecL >=> mMatch env o >=> mMatch env lhs
      go (SectionR _ o rhs) = mapFor emSecR >=> mMatch env o >=> mMatch env rhs
      go (HsLet _ lbs e') =
#endif
        let
          bs = collectLocalBinders (unLoc lbs)
          env' = extendMatchEnv env bs
        in mapFor emLet >=> mMatch env (unLoc lbs) >=> mMatch env' e'
      go (ExplicitList _ _ es) = mapFor emExplicitList >=> mMatch env es
#if __GLASGOW_HASKELL__ < 806
      go (ExprWithTySig e' (HsWC _ (HsIB _ ty _))) =
#elif __GLASGOW_HASKELL__ < 808
      go (ExprWithTySig (HsWC _ (HsIB _ ty)) e') =
#else
      go (ExprWithTySig _ e' (HsWC _ (HsIB _ ty))) =
#endif
        mapFor emExprWithTySig >=> mMatch env e' >=> mMatch env ty
      go _ = const [] -- TODO remove

-- Add the matched expression to the holes map, fails if expression differs from one already in hole.
extendResult :: Map RdrName a -> HoleVal -> Substitution -> [(Substitution, a)]
extendResult hm v sub = catMaybes
  [ case lookupSubst n sub of
      Nothing -> return (extendSubst sub n v, x)
      Just v' -> sameHoleValue v v' >> return (sub, x)
  | (nm,x) <- mapAssocs hm, let n = rdrFS nm ]

singleton :: [a] -> Maybe a
singleton [x] = Just x
singleton _  = Nothing

-- | Determine if two expressions are alpha-equivalent.
sameHoleValue :: HoleVal -> HoleVal -> Maybe ()
sameHoleValue (HoleExpr e1)  (HoleExpr e2)  =
  alphaEquivalent (astA e1) (astA e2) EMEmpty
sameHoleValue (HolePat p1)   (HolePat p2)   =
  alphaEquivalent
#if __GLASGOW_HASKELL__ < 808
    (astA p1)
    (astA p2)
#else
    (composeSrcSpan $ astA p1)
    (composeSrcSpan $ astA p2)
#endif
    PatEmpty
sameHoleValue (HoleType ty1) (HoleType ty2) =
  alphaEquivalent (astA ty1) (astA ty2) TyEmpty
sameHoleValue _              _              = Nothing

alphaEquivalent :: PatternMap m => Key m -> Key m -> m () -> Maybe ()
alphaEquivalent v1 v2 e = snd <$> singleton (findMatch env v2 m)
  where
    m = insertMatch emptyAlphaEnv emptyQs v1 () e
    env = ME emptyAlphaEnv err
    err _ = error "hole prune during alpha-equivalence check is impossible!"

------------------------------------------------------------------------

data SCMap a
  = SCEmpty
  | SCM { scmListComp :: MaybeMap a
        , scmMonadComp :: MaybeMap a
        , scmDoExpr :: MaybeMap a
        -- TODO: the rest
        }
  deriving (Functor)

emptySCMapWrapper :: SCMap a
emptySCMapWrapper = SCM mEmpty mEmpty mEmpty

instance PatternMap SCMap where
  type Key SCMap = HsStmtContext Name -- see comment on HsDo in GHC

  mEmpty :: SCMap a
  mEmpty = SCEmpty

  mUnion :: SCMap a -> SCMap a -> SCMap a
  mUnion SCEmpty m = m
  mUnion m SCEmpty = m
  mUnion m1 m2 = SCM
    { scmListComp = unionOn scmListComp m1 m2
    , scmMonadComp = unionOn scmMonadComp m1 m2
    , scmDoExpr = unionOn scmDoExpr m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key SCMap -> A a -> SCMap a -> SCMap a
  mAlter env vs sc f SCEmpty = mAlter env vs sc f emptySCMapWrapper
  mAlter env vs sc f m@SCM{} = go sc
    where
      go ListComp = m { scmListComp = mAlter env vs () f (scmListComp m) }
      go MonadComp = m { scmMonadComp = mAlter env vs () f (scmMonadComp m) }
#if __GLASGOW_HASKELL__ < 806
      go PArrComp = missingSyntax "PArrComp"
#endif
      go DoExpr = m { scmDoExpr = mAlter env vs () f (scmDoExpr m) }
      go MDoExpr = missingSyntax "MDoExpr"
      go ArrowExpr = missingSyntax "ArrowExpr"
      go GhciStmtCtxt = missingSyntax "GhciStmtCtxt"
      go (PatGuard _) = missingSyntax "PatGuard"
      go (ParStmtCtxt _) = missingSyntax "ParStmtCtxt"
      go (TransStmtCtxt _) = missingSyntax "TransStmtCtxt"

  mMatch :: MatchEnv -> Key SCMap -> (Substitution, SCMap a) -> [(Substitution, a)]
  mMatch _   _  (_,SCEmpty)  = []
  mMatch env sc (hs,m@SCM{}) = go sc (hs,m)
    where
      go ListComp = mapFor scmListComp >=> mMatch env ()
      go MonadComp = mapFor scmMonadComp >=> mMatch env ()
      go DoExpr = mapFor scmDoExpr >=> mMatch env ()
      go _ = const [] -- TODO

------------------------------------------------------------------------

-- Note [MatchGroup]
-- A MatchGroup contains a list of argument types and a result type, but
-- these aren't available until after typechecking, so they are all placeholders
-- at this point. Also, don't care about the origin.
newtype MGMap a = MGMap { unMGMap :: ListMap MMap a }
  deriving (Functor)

instance PatternMap MGMap where
  type Key MGMap = MatchGroup GhcPs (LHsExpr GhcPs)

  mEmpty :: MGMap a
  mEmpty = MGMap mEmpty

  mUnion :: MGMap a -> MGMap a -> MGMap a
  mUnion (MGMap m1) (MGMap m2) = MGMap (mUnion m1 m2)

  mAlter :: AlphaEnv -> Quantifiers -> Key MGMap -> A a -> MGMap a -> MGMap a
  mAlter env vs mg f (MGMap m) = MGMap (mAlter env vs alts f m)
    where alts = map unLoc (unLoc $ mg_alts mg)

  mMatch :: MatchEnv -> Key MGMap -> (Substitution, MGMap a) -> [(Substitution, a)]
  mMatch env mg = mapFor unMGMap >=> mMatch env alts
    where alts = map unLoc (unLoc $ mg_alts mg)

------------------------------------------------------------------------

newtype MMap a = MMap { unMMap :: ListMap PatMap (GRHSSMap a) }
  deriving (Functor)

instance PatternMap MMap where
  type Key MMap = Match GhcPs (LHsExpr GhcPs)

  mEmpty :: MMap a
  mEmpty = MMap mEmpty

  mUnion :: MMap a -> MMap a -> MMap a
  mUnion (MMap m1) (MMap m2) = MMap (mUnion m1 m2)

  mAlter :: AlphaEnv -> Quantifiers -> Key MMap -> A a -> MMap a -> MMap a
  mAlter env vs match f (MMap m) =
    let lpats = m_pats match
        pbs = collectPatsBinders lpats
        env' = foldr extendAlphaEnvInternal env pbs
        vs' = vs `exceptQ` pbs
    in MMap (mAlter env vs lpats
              (toA (mAlter env' vs' (m_grhss match) f)) m)

  mMatch :: MatchEnv -> Key MMap -> (Substitution, MMap a) -> [(Substitution, a)]
  mMatch env match = mapFor unMMap >=> mMatch env lpats >=> mMatch env' (m_grhss match)
    where
      lpats = m_pats match
      pbs = collectPatsBinders lpats
      env' = extendMatchEnv env pbs

------------------------------------------------------------------------

data CDMap a
  = CDEmpty
  | CDMap { cdPrefixCon :: ListMap PatMap a
          -- TODO , cdRecCon    :: MaybeMap a
          , cdInfixCon  :: PatMap (PatMap a)
          }
  deriving (Functor)

emptyCDMapWrapper :: CDMap a
emptyCDMapWrapper = CDMap mEmpty mEmpty

instance PatternMap CDMap where
#if __GLASGOW_HASKELL__ < 810
  type Key CDMap = HsConDetails (LPat GhcPs) (HsRecFields GhcPs (LPat GhcPs))
#else
  -- We must manually expand 'LPat' to avoid UndecidableInstances in GHC 8.10+
  type Key CDMap = HsConDetails (Located (Pat GhcPs)) (HsRecFields GhcPs (Located (Pat GhcPs)))
#endif

  mEmpty :: CDMap a
  mEmpty = CDEmpty

  mUnion :: CDMap a -> CDMap a -> CDMap a
  mUnion CDEmpty m = m
  mUnion m CDEmpty = m
  mUnion m1 m2 = CDMap
    { cdPrefixCon = unionOn cdPrefixCon m1 m2
    , cdInfixCon = unionOn cdInfixCon m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key CDMap -> A a -> CDMap a -> CDMap a
  mAlter env vs d f CDEmpty   = mAlter env vs d f emptyCDMapWrapper
  mAlter env vs d f m@CDMap{} = go d
    where
      go (PrefixCon ps) = m { cdPrefixCon = mAlter env vs ps f (cdPrefixCon m) }
      go (RecCon _) = missingSyntax "RecCon"
      go (InfixCon p1 p2) = m { cdInfixCon = mAlter env vs p1
                                              (toA (mAlter env vs p2 f))
                                              (cdInfixCon m) }

  mMatch :: MatchEnv -> Key CDMap -> (Substitution, CDMap a) -> [(Substitution, a)]
  mMatch _   _ (_ ,CDEmpty)   = []
  mMatch env d (hs,m@CDMap{}) = go d (hs,m)
    where
      go (PrefixCon ps) = mapFor cdPrefixCon >=> mMatch env ps
      go (InfixCon p1 p2) = mapFor cdInfixCon >=> mMatch env p1 >=> mMatch env p2
      go _ = const [] -- TODO

------------------------------------------------------------------------

-- Note [Variable Binders]
-- We don't actually care about the variable name, since we are checking for
-- alpha-equivalence.

data PatMap a
  = PatEmpty
  | PatMap { pmHole :: Map RdrName a -- See Note [Holes]
           , pmWild :: MaybeMap a
           , pmVar  :: MaybeMap a -- See Note [Variable Binders]
           , pmParPat :: PatMap a
           , pmTuplePat :: BoxityMap (ListMap PatMap a)
           , pmConPatIn :: FSEnv (CDMap a)
           -- TODO: the rest
           }
  deriving (Functor)

emptyPatMapWrapper :: PatMap a
emptyPatMapWrapper = PatMap mEmpty mEmpty mEmpty mEmpty mEmpty mEmpty

instance PatternMap PatMap where
#if __GLASGOW_HASKELL__ < 810
  type Key PatMap = LPat GhcPs
#else
  -- We must manually expand 'LPat' to avoid UndecidableInstances in GHC 8.10+
  type Key PatMap = Located (Pat GhcPs)
#endif

  mEmpty :: PatMap a
  mEmpty = PatEmpty

  mUnion :: PatMap a -> PatMap a -> PatMap a
  mUnion PatEmpty m = m
  mUnion m PatEmpty = m
  mUnion m1 m2 = PatMap
    { pmHole = unionOn pmHole m1 m2
    , pmWild = unionOn pmWild m1 m2
    , pmVar = unionOn pmVar m1 m2
    , pmParPat = unionOn pmParPat m1 m2
    , pmTuplePat = unionOn pmTuplePat m1 m2
    , pmConPatIn = unionOn pmConPatIn m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key PatMap -> A a -> PatMap a -> PatMap a
  mAlter env vs pat f PatEmpty   = mAlter env vs pat f emptyPatMapWrapper
  mAlter env vs pat f m@PatMap{} = go (unLoc pat)
    where
      go (WildPat _) = m { pmWild = mAlter env vs () f (pmWild m) }
#if __GLASGOW_HASKELL__ < 806
      go (VarPat v)
#else
      go (VarPat _ v)
#endif
        | unLoc v `isQ` vs = m { pmHole  = mAlter env vs (unLoc v) f (pmHole m) }
        | otherwise        = m { pmVar   = mAlter env vs () f (pmVar m) } -- See Note [Variable Binders]
      go LazyPat{} = missingSyntax "LazyPat"
      go AsPat{} = missingSyntax "AsPat"
      go BangPat{} = missingSyntax "BangPat"
      go ListPat{} = missingSyntax "ListPat"
      go (ConPatIn c d) = m { pmConPatIn = mAlter env vs (rdrFS (unLoc c)) (toA (mAlter env vs d f)) (pmConPatIn m) }
      go ConPatOut{} = missingSyntax "ConPatOut"
      go ViewPat{} = missingSyntax "ViewPat"
      go SplicePat{} = missingSyntax "SplicePat"
      go LitPat{} = missingSyntax "LitPat"
      go NPat{} = missingSyntax "NPat"
      go NPlusKPat{} = missingSyntax "NPlusKPat"
#if __GLASGOW_HASKELL__ < 806
      go (PArrPat _ _) = missingSyntax "PArrPat"
      go (ParPat p) = m { pmParPat = mAlter env vs p f (pmParPat m) }
      go (SigPatIn _ _) = missingSyntax "SigPatIn"
      go (SigPatOut _ _) = missingSyntax "SigPatOut"
      go (TuplePat ps b _tys) =
        m { pmTuplePat = mAlter env vs b (toA (mAlter env vs ps f)) (pmTuplePat m) }
#else
      go (ParPat _ p) = m { pmParPat = mAlter env vs p f (pmParPat m) }
      go (TuplePat _ ps b) =
        m { pmTuplePat = mAlter env vs b (toA (mAlter env vs ps f)) (pmTuplePat m) }
      go SigPat{} = missingSyntax "SigPat"
      go XPat{} = missingSyntax "XPat"
#endif
      go CoPat{} = missingSyntax "CoPat"
      go SumPat{} = missingSyntax "SumPat"

  mMatch :: MatchEnv -> Key PatMap -> (Substitution, PatMap a) -> [(Substitution, a)]
  mMatch _   _   (_ ,PatEmpty)   = []
#if __GLASGOW_HASKELL__ < 808
  mMatch env pat (hs,m@PatMap{}) =
#else
  mMatch env (dL -> pat) (hs,m@PatMap{}) =
#endif
    hss ++ go (unLoc pat) (hs,m)
    where
      hss = extendResult (pmHole m) (HolePat $ mePruneA env pat) hs

      go (WildPat _) = mapFor pmWild >=> mMatch env ()
#if __GLASGOW_HASKELL__ < 806
      go (ParPat p) = mapFor pmParPat >=> mMatch env p
      go (TuplePat ps b _) = mapFor pmTuplePat >=> mMatch env b >=> mMatch env ps
      go (VarPat _) = mapFor pmVar >=> mMatch env ()
#else
      go (ParPat _ p) = mapFor pmParPat >=> mMatch env p
      go (TuplePat _ ps b) = mapFor pmTuplePat >=> mMatch env b >=> mMatch env ps
      go (VarPat _ _) = mapFor pmVar >=> mMatch env ()
#endif
      go (ConPatIn c d) = mapFor pmConPatIn >=> mMatch env (rdrFS (unLoc c)) >=> mMatch env d
      go _ = const [] -- TODO

------------------------------------------------------------------------

newtype GRHSSMap a = GRHSSMap { unGRHSSMap :: LBMap (ListMap GRHSMap a) }
  deriving (Functor)

instance PatternMap GRHSSMap where
  type Key GRHSSMap = GRHSs GhcPs (LHsExpr GhcPs)

  mEmpty :: GRHSSMap a
  mEmpty = GRHSSMap mEmpty

  mUnion :: GRHSSMap a -> GRHSSMap a -> GRHSSMap a
  mUnion (GRHSSMap m1) (GRHSSMap m2) = GRHSSMap (mUnion m1 m2)

  mAlter :: AlphaEnv -> Quantifiers -> Key GRHSSMap -> A a -> GRHSSMap a -> GRHSSMap a
  mAlter env vs grhss f (GRHSSMap m) =
    let lbs = unLoc $ grhssLocalBinds grhss
        bs = collectLocalBinders lbs
        env' = foldr extendAlphaEnvInternal env bs
        vs' = vs `exceptQ` bs
    in GRHSSMap (mAlter env vs lbs
                  (toA (mAlter env' vs' (map unLoc $ grhssGRHSs grhss) f)) m)

  mMatch :: MatchEnv -> Key GRHSSMap -> (Substitution, GRHSSMap a) -> [(Substitution, a)]
  mMatch env grhss = mapFor unGRHSSMap >=> mMatch env lbs
                      >=> mMatch env' (map unLoc $ grhssGRHSs grhss)
    where
      lbs = unLoc $ grhssLocalBinds grhss
      bs = collectLocalBinders lbs
      env' = extendMatchEnv env bs

------------------------------------------------------------------------

newtype GRHSMap a = GRHSMap { unGRHSMap :: SLMap (EMap a) }
  deriving (Functor)

instance PatternMap GRHSMap where
  type Key GRHSMap = GRHS GhcPs (LHsExpr GhcPs)

  mEmpty :: GRHSMap a
  mEmpty = GRHSMap mEmpty

  mUnion :: GRHSMap a -> GRHSMap a -> GRHSMap a
  mUnion (GRHSMap m1) (GRHSMap m2) = GRHSMap (mUnion m1 m2)

  mAlter :: AlphaEnv -> Quantifiers -> Key GRHSMap -> A a -> GRHSMap a -> GRHSMap a
#if __GLASGOW_HASKELL__ < 806
  mAlter env vs (GRHS gs b) f (GRHSMap m) =
#else
  mAlter _ _ XGRHS{} _ _ = missingSyntax "XGRHS"
  mAlter env vs (GRHS _ gs b) f (GRHSMap m) =
#endif
    let bs = collectLStmtsBinders gs
        env' = foldr extendAlphaEnvInternal env bs
        vs' = vs `exceptQ` bs
    in GRHSMap (mAlter env vs gs (toA (mAlter env' vs' b f)) m)

  mMatch :: MatchEnv -> Key GRHSMap -> (Substitution, GRHSMap a) -> [(Substitution, a)]
#if __GLASGOW_HASKELL__ < 806
  mMatch env (GRHS gs b) =
#else
  mMatch _ XGRHS{} = const []
  mMatch env (GRHS _ gs b) =
#endif
    mapFor unGRHSMap >=> mMatch env gs >=> mMatch env' b
    where
      bs = collectLStmtsBinders gs
      env' = extendMatchEnv env bs

------------------------------------------------------------------------

data SLMap a
  = SLEmpty
  | SLM { slmNil :: MaybeMap a
        , slmCons :: SMap (SLMap a)
        }
  deriving (Functor)

emptySLMapWrapper :: SLMap a
emptySLMapWrapper = SLM mEmpty mEmpty

instance PatternMap SLMap where
  type Key SLMap = [LStmt GhcPs (LHsExpr GhcPs)]

  mEmpty :: SLMap a
  mEmpty = SLEmpty

  mUnion :: SLMap a -> SLMap a -> SLMap a
  mUnion SLEmpty m = m
  mUnion m SLEmpty = m
  mUnion m1 m2 = SLM
    { slmNil = unionOn slmNil m1 m2
    , slmCons = unionOn slmCons m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key SLMap -> A a -> SLMap a -> SLMap a
  mAlter env vs ss f SLEmpty = mAlter env vs ss f emptySLMapWrapper
  mAlter env vs ss f m@SLM{} = go ss
    where
      go []      = m { slmNil = mAlter env vs () f (slmNil m) }
      go (s:ss') =
        let
          bs = collectLStmtBinders s
          env' = foldr extendAlphaEnvInternal env bs
          vs' = vs `exceptQ` bs
        in m { slmCons = mAlter env vs s (toA (mAlter env' vs' ss' f)) (slmCons m) }

  mMatch :: MatchEnv -> Key SLMap -> (Substitution, SLMap a) -> [(Substitution, a)]
  mMatch _   _  (_,SLEmpty)  = []
  mMatch env ss (hs,m@SLM{}) = go ss (hs,m)
    where
      go [] = mapFor slmNil >=> mMatch env ()
      go (s:ss') =
        let
          bs = collectLStmtBinders s
          env' = extendMatchEnv env bs
        in mapFor slmCons >=> mMatch env s >=> mMatch env' ss'

------------------------------------------------------------------------

-- Note [Local Binds]
-- We simplify this a bit here, assuming always ValBindsIn (because ValBindsOut
-- only shows up after renaming. Also we ignore the [LSig] for now.

data LBMap a
  = LBEmpty
  | LB { lbValBinds :: ListMap BMap a -- see Note [Local Binds]
       -- TODO: , lbIPBinds ::
       , lbEmpty :: MaybeMap a
       }
  deriving (Functor)

emptyLBMapWrapper :: LBMap a
emptyLBMapWrapper = LB mEmpty mEmpty

instance PatternMap LBMap where
  type Key LBMap = HsLocalBinds GhcPs

  mEmpty :: LBMap a
  mEmpty = LBEmpty

  mUnion :: LBMap a -> LBMap a -> LBMap a
  mUnion LBEmpty m = m
  mUnion m LBEmpty = m
  mUnion m1 m2 = LB
    { lbValBinds = unionOn lbValBinds m1 m2
    , lbEmpty = unionOn lbEmpty m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key LBMap -> A a -> LBMap a -> LBMap a
  mAlter env vs lbs f LBEmpty = mAlter env vs lbs f emptyLBMapWrapper
  mAlter env vs lbs f m@LB{}  = go lbs
    where
#if __GLASGOW_HASKELL__ < 806
      go EmptyLocalBinds = m { lbEmpty = mAlter env vs () f (lbEmpty m) }
      go (HsValBinds vbs) =
#else
      go (EmptyLocalBinds _) = m { lbEmpty = mAlter env vs () f (lbEmpty m) }
      go XHsLocalBindsLR{} = missingSyntax "XHsLocalBindsLR"
      go (HsValBinds _ vbs) =
#endif
        let
          bs = collectHsValBinders vbs
          env' = foldr extendAlphaEnvInternal env bs
          vs' = vs `exceptQ` bs
        in m { lbValBinds = mAlter env' vs' (deValBinds vbs) f (lbValBinds m) }
      go HsIPBinds{} = missingSyntax "HsIPBinds"

  mMatch :: MatchEnv -> Key LBMap -> (Substitution, LBMap a) -> [(Substitution, a)]
  mMatch _   _   (_,LBEmpty) = []
  mMatch env lbs (hs,m@LB{}) = go lbs (hs,m)
    where
#if __GLASGOW_HASKELL__ < 806
      go EmptyLocalBinds = mapFor lbEmpty >=> mMatch env ()
      go (HsValBinds vbs) =
#else
      go (EmptyLocalBinds _) = mapFor lbEmpty >=> mMatch env ()
      go (HsValBinds _ vbs) =
#endif
        let
          bs = collectHsValBinders vbs
          env' = extendMatchEnv env bs
        in mapFor lbValBinds >=> mMatch env' (deValBinds vbs)
      go _ = const [] -- TODO

deValBinds :: HsValBinds GhcPs -> [HsBind GhcPs]
#if __GLASGOW_HASKELL__ < 806
deValBinds (ValBindsIn lbs _) = map unLoc (bagToList lbs)
#else
deValBinds (ValBinds _ lbs _) = map unLoc (bagToList lbs)
#endif
deValBinds _ = error "deValBinds ValBindsOut"

------------------------------------------------------------------------

-- Note [Bind env]
-- We don't extend the env because it was already done at the LBMap level
-- (because all bindings are available to the recursive group).

data BMap a
  = BMEmpty
  | BM { bmFunBind :: MGMap a
       , bmVarBind :: EMap a
       , bmPatBind :: PatMap (GRHSSMap a)
       -- TODO: rest
       }
  deriving (Functor)

emptyBMapWrapper :: BMap a
emptyBMapWrapper = BM mEmpty mEmpty mEmpty

instance PatternMap BMap where
  type Key BMap = HsBind GhcPs

  mEmpty :: BMap a
  mEmpty = BMEmpty

  mUnion :: BMap a -> BMap a -> BMap a
  mUnion BMEmpty m = m
  mUnion m BMEmpty = m
  mUnion m1 m2 = BM
    { bmFunBind = unionOn bmFunBind m1 m2
    , bmVarBind = unionOn bmVarBind m1 m2
    , bmPatBind = unionOn bmPatBind m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key BMap -> A a -> BMap a -> BMap a
  mAlter env vs b f BMEmpty = mAlter env vs b f emptyBMapWrapper
  mAlter env vs b f m@BM{}  = go b
    where -- see Note [Bind env]
#if __GLASGOW_HASKELL__ < 806
      go (FunBind _ mg _ _ _) = m { bmFunBind = mAlter env vs mg f (bmFunBind m) }
      go (VarBind _ e _) = m { bmVarBind = mAlter env vs e f (bmVarBind m) }
      go (PatBind lhs rhs _ _ _) =
#else
      go (FunBind _ _ mg _ _) = m { bmFunBind = mAlter env vs mg f (bmFunBind m) }
      go (VarBind _ _ e _) = m { bmVarBind = mAlter env vs e f (bmVarBind m) }
      go XHsBindsLR{} = missingSyntax "XHsBindsLR"
      go (PatBind _ lhs rhs _) =
#endif
        m { bmPatBind = mAlter env vs lhs
              (toA $ mAlter env vs rhs f) (bmPatBind m) }
      go AbsBinds{} = missingSyntax "AbsBinds"
      go PatSynBind{} = missingSyntax "PatSynBind"

  mMatch :: MatchEnv -> Key BMap -> (Substitution, BMap a) -> [(Substitution, a)]
  mMatch _   _ (_,BMEmpty) = []
  mMatch env b (hs,m@BM{}) = go b (hs,m)
    where
#if __GLASGOW_HASKELL__ < 806
      go (FunBind _ mg _ _ _) = mapFor bmFunBind >=> mMatch env mg
      go (VarBind _ e _) = mapFor bmVarBind >=> mMatch env e
      go (PatBind lhs rhs _ _ _)
#else
      go (FunBind _ _ mg _ _)  = mapFor bmFunBind >=> mMatch env mg
      go (VarBind _ _ e _) = mapFor bmVarBind >=> mMatch env e
      go (PatBind _ lhs rhs _)
#endif
        = mapFor bmPatBind >=> mMatch env lhs >=> mMatch env rhs
      go _ = const [] -- TODO

------------------------------------------------------------------------

data SMap a
  = SMEmpty
  | SM { smLastStmt :: EMap a
       , smBindStmt :: PatMap (EMap a)
       , smBodyStmt :: EMap a
         -- TODO: the rest
       }
  deriving (Functor)

emptySMapWrapper :: SMap a
emptySMapWrapper = SM mEmpty mEmpty mEmpty

instance PatternMap SMap where
  type Key SMap = LStmt GhcPs (LHsExpr GhcPs)

  mEmpty :: SMap a
  mEmpty = SMEmpty

  mUnion :: SMap a -> SMap a -> SMap a
  mUnion SMEmpty m = m
  mUnion m SMEmpty = m
  mUnion m1 m2 = SM
    { smLastStmt = unionOn smLastStmt m1 m2
    , smBindStmt = unionOn smBindStmt m1 m2
    , smBodyStmt = unionOn smBodyStmt m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key SMap -> A a -> SMap a -> SMap a
  mAlter env vs s f SMEmpty = mAlter env vs s f emptySMapWrapper
  mAlter env vs s f m@(SM {}) = go (unLoc s)
    where
#if __GLASGOW_HASKELL__ < 806
      go (BodyStmt e _ _ _) = m { smBodyStmt = mAlter env vs e f (smBodyStmt m) }
      go (LastStmt e _ _)   = m { smLastStmt = mAlter env vs e f (smLastStmt m) }
      go (BindStmt p e _ _ _) =
#else
      go (BodyStmt _ e _ _) = m { smBodyStmt = mAlter env vs e f (smBodyStmt m) }
      go (LastStmt _ e _ _)   = m { smLastStmt = mAlter env vs e f (smLastStmt m) }
      go XStmtLR{} = missingSyntax "XStmtLR"
      go (BindStmt _ p e _ _) =
#endif
        let bs = collectPatBinders p
            env' = foldr extendAlphaEnvInternal env bs
            vs' = vs `exceptQ` bs
        in m { smBindStmt = mAlter env vs p
                              (toA (mAlter env' vs' e f)) (smBindStmt m) }
      go LetStmt{} = missingSyntax "LetStmt"
      go ParStmt{} = missingSyntax "ParStmt"
      go TransStmt{} = missingSyntax "TransStmt"
      go RecStmt{} = missingSyntax "RecStmt"
      go ApplicativeStmt{} = missingSyntax "ApplicativeStmt"

  mMatch :: MatchEnv -> Key SMap -> (Substitution, SMap a) -> [(Substitution, a)]
  mMatch _   _   (_,SMEmpty) = []
  mMatch env s   (hs,m) = go (unLoc s) (hs,m)
    where
#if __GLASGOW_HASKELL__ < 806
      go (BodyStmt e _ _ _) = mapFor smBodyStmt >=> mMatch env e
      go (LastStmt e _ _) = mapFor smLastStmt >=> mMatch env e
      go (BindStmt p e _ _ _) =
#else
      go (BodyStmt _ e _ _) = mapFor smBodyStmt >=> mMatch env e
      go (LastStmt _ e _ _) = mapFor smLastStmt >=> mMatch env e
      go (BindStmt _ p e _ _) =
#endif
        let bs = collectPatBinders p
            env' = extendMatchEnv env bs
        in mapFor smBindStmt >=> mMatch env p >=> mMatch env' e
      go _ = const [] -- TODO

------------------------------------------------------------------------

data TyMap a
  = TyEmpty
  | TM { tyHole    :: Map RdrName a -- See Note [Holes]
       , tyHsTyVar :: VMap a
       , tyHsAppTy :: TyMap (TyMap a)
#if __GLASGOW_HASKELL__ < 806
       , tyHsAppsTy :: ListMap AppTyMap a
#endif
#if __GLASGOW_HASKELL__ < 810
       , tyHsForAllTy :: ForAllTyMap a -- See Note [Telescope]
#else
       , tyHsForAllTy :: ForallVisMap (ForAllTyMap a) -- See Note [Telescope]
#endif
       , tyHsFunTy :: TyMap (TyMap a)
       , tyHsListTy :: TyMap a
       , tyHsParTy :: TyMap a
       , tyHsQualTy :: TyMap (ListMap TyMap a)
       , tyHsSumTy :: ListMap TyMap a
       , tyHsTupleTy :: TupleSortMap (ListMap TyMap a)
         -- TODO: the rest
       }
  deriving (Functor)

emptyTyMapWrapper :: TyMap a
emptyTyMapWrapper = TM
  mEmpty mEmpty mEmpty
#if __GLASGOW_HASKELL__ < 806
  mEmpty
#endif
  mEmpty mEmpty mEmpty mEmpty mEmpty mEmpty mEmpty

instance PatternMap TyMap where
  type Key TyMap = LHsType GhcPs

  mEmpty :: TyMap a
  mEmpty = TyEmpty

  mUnion :: TyMap a -> TyMap a -> TyMap a
  mUnion TyEmpty m = m
  mUnion m TyEmpty = m
  mUnion m1 m2 = TM
    { tyHole = unionOn tyHole m1 m2
    , tyHsTyVar = unionOn tyHsTyVar m1 m2
    , tyHsAppTy = unionOn tyHsAppTy m1 m2
#if __GLASGOW_HASKELL__ < 806
    , tyHsAppsTy = unionOn tyHsAppsTy m1 m2
#endif
    , tyHsForAllTy = unionOn tyHsForAllTy m1 m2
    , tyHsFunTy = unionOn tyHsFunTy m1 m2
    , tyHsListTy = unionOn tyHsListTy m1 m2
    , tyHsParTy = unionOn tyHsParTy m1 m2
    , tyHsQualTy = unionOn tyHsQualTy m1 m2
    , tyHsSumTy = unionOn tyHsSumTy m1 m2
    , tyHsTupleTy = unionOn tyHsTupleTy m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key TyMap -> A a -> TyMap a -> TyMap a
  mAlter env vs ty f TyEmpty = mAlter env vs ty f emptyTyMapWrapper
#if __GLASGOW_HASKELL__ < 806
  mAlter env vs (tyLookThrough -> ty) f m@(TM {}) =
#else
  mAlter env vs ty f m@(TM {}) =
#endif
    go (unLoc ty) -- See Note [TyVar Quantifiers]
    where
#if __GLASGOW_HASKELL__ < 806
      go (HsTyVar _ (L _ v))
#else
      go (HsTyVar _ _ (L _ v))
#endif
        | v `isQ` vs = m { tyHole    = mAlter env vs v f (tyHole m) }
        | otherwise  = m { tyHsTyVar = mAlter env vs v f (tyHsTyVar m) }
      go HsOpTy{} = missingSyntax "HsOpTy"
      go HsIParamTy{} = missingSyntax "HsIParamTy"
      go HsKindSig{} = missingSyntax "HsKindSig"
      go HsSpliceTy{} = missingSyntax "HsSpliceTy"
      go HsDocTy{} = missingSyntax "HsDocTy"
      go HsBangTy{} = missingSyntax "HsBangTy"
      go HsRecTy{} = missingSyntax "HsRecTy"
#if __GLASGOW_HASKELL__ < 806
      go (HsAppsTy atys) = m { tyHsAppsTy = mAlter env vs atys f (tyHsAppsTy m) }
      go (HsAppTy ty1 ty2) = m { tyHsAppTy = mAlter env vs ty1 (toA (mAlter env vs ty2 f)) (tyHsAppTy m) }
      go (HsCoreTy _) = missingSyntax "HsCoreTy"
      go (HsEqTy _ _) = missingSyntax "HsEqTy"
      go (HsForAllTy bndrs ty') = m { tyHsForAllTy = mAlter env vs (bndrs, ty') f (tyHsForAllTy m) }
      go (HsFunTy ty1 ty2) = m { tyHsFunTy = mAlter env vs ty1 (toA (mAlter env vs ty2 f)) (tyHsFunTy m) }
      go (HsListTy ty') = m { tyHsListTy = mAlter env vs ty' f (tyHsListTy m) }
      go (HsParTy ty') = m { tyHsParTy = mAlter env vs ty' f (tyHsParTy m) }
      go (HsPArrTy _) = missingSyntax "HsPArrTy"
      go (HsQualTy (L _ cons) ty') =
        m { tyHsQualTy = mAlter env vs ty' (toA (mAlter env vs cons f)) (tyHsQualTy m) }
      go (HsSumTy tys) = m { tyHsSumTy = mAlter env vs tys f (tyHsSumTy m) }
      go (HsTupleTy ts tys) =
        m { tyHsTupleTy = mAlter env vs ts (toA (mAlter env vs tys f)) (tyHsTupleTy m) }
#else
      go (HsAppTy _ ty1 ty2) = m { tyHsAppTy = mAlter env vs ty1 (toA (mAlter env vs ty2 f)) (tyHsAppTy m) }
#if __GLASGOW_HASKELL__ < 810
      go (HsForAllTy _ bndrs ty') = m { tyHsForAllTy = mAlter env vs (bndrs, ty') f (tyHsForAllTy m) }
#else
      go (HsForAllTy _ vis bndrs ty') =
        m { tyHsForAllTy = mAlter env vs vis (toA (mAlter env vs (bndrs, ty') f)) (tyHsForAllTy m) }
#endif
      go (HsFunTy _ ty1 ty2) = m { tyHsFunTy = mAlter env vs ty1 (toA (mAlter env vs ty2 f)) (tyHsFunTy m) }
      go (HsListTy _ ty') = m { tyHsListTy = mAlter env vs ty' f (tyHsListTy m) }
      go (HsParTy _ ty') = m { tyHsParTy = mAlter env vs ty' f (tyHsParTy m) }
      go (HsQualTy _ (L _ cons) ty') =
        m { tyHsQualTy = mAlter env vs ty' (toA (mAlter env vs cons f)) (tyHsQualTy m) }
      go HsStarTy{} = missingSyntax "HsStarTy"
      go (HsSumTy _ tys) = m { tyHsSumTy = mAlter env vs tys f (tyHsSumTy m) }
      go (HsTupleTy _ ts tys) =
        m { tyHsTupleTy = mAlter env vs ts (toA (mAlter env vs tys f)) (tyHsTupleTy m) }
      go XHsType{} = missingSyntax "XHsType"
#endif
      go HsExplicitListTy{} = missingSyntax "HsExplicitListTy"
      go HsExplicitTupleTy{} = missingSyntax "HsExplicitTupleTy"
      go HsTyLit{} = missingSyntax "HsTyLit"
      go HsWildCardTy{} = missingSyntax "HsWildCardTy"
#if __GLASGOW_HASKELL__ < 808
#else
      go HsAppKindTy{} = missingSyntax "HsAppKindTy"
#endif

  mMatch :: MatchEnv -> Key TyMap -> (Substitution, TyMap a) -> [(Substitution, a)]
  mMatch _   _  (_,TyEmpty) = []
#if __GLASGOW_HASKELL__ < 806
  mMatch env (tyLookThrough -> ty) (hs,m@TM{}) =
#else
  mMatch env ty (hs,m@TM{}) =
#endif
    hss ++ go (unLoc ty) (hs,m) -- See Note [TyVar Quantifiers]
    where
      hss = extendResult (tyHole m) (HoleType $ mePruneA env ty) hs

#if __GLASGOW_HASKELL__ < 806
      go (HsAppTy ty1 ty2) = mapFor tyHsAppTy >=> mMatch env ty1 >=> mMatch env ty2
      go (HsAppsTy atys) = mapFor tyHsAppsTy >=> mMatch env atys
      go (HsForAllTy bndrs ty') = mapFor tyHsForAllTy >=> mMatch env (bndrs, ty')
      go (HsFunTy ty1 ty2) = mapFor tyHsFunTy >=> mMatch env ty1 >=> mMatch env ty2
      go (HsListTy ty') = mapFor tyHsListTy >=> mMatch env ty'
      go (HsParTy ty') = mapFor tyHsParTy >=> mMatch env ty'
      go (HsQualTy (L _ cons) ty') = mapFor tyHsQualTy >=> mMatch env ty' >=> mMatch env cons
      go (HsSumTy tys) = mapFor tyHsSumTy >=> mMatch env tys
      go (HsTupleTy ts tys) = mapFor tyHsTupleTy >=> mMatch env ts >=> mMatch env tys
      go (HsTyVar _ v) = mapFor tyHsTyVar >=> mMatch env (unLoc v)
#else
      go (HsAppTy _ ty1 ty2) = mapFor tyHsAppTy >=> mMatch env ty1 >=> mMatch env ty2
#if __GLASGOW_HASKELL__ < 810
      go (HsForAllTy _ bndrs ty') = mapFor tyHsForAllTy >=> mMatch env (bndrs, ty')
#else
      go (HsForAllTy _ vis bndrs ty') =
        mapFor tyHsForAllTy >=> mMatch env vis >=> mMatch env (bndrs, ty')
#endif
      go (HsFunTy _ ty1 ty2) = mapFor tyHsFunTy >=> mMatch env ty1 >=> mMatch env ty2
      go (HsListTy _ ty') = mapFor tyHsListTy >=> mMatch env ty'
      go (HsParTy _ ty') = mapFor tyHsParTy >=> mMatch env ty'
      go (HsQualTy _ (L _ cons) ty') = mapFor tyHsQualTy >=> mMatch env ty' >=> mMatch env cons
      go (HsSumTy _ tys) = mapFor tyHsSumTy >=> mMatch env tys
      go (HsTupleTy _ ts tys) = mapFor tyHsTupleTy >=> mMatch env ts >=> mMatch env tys
      go (HsTyVar _ _ v) = mapFor tyHsTyVar >=> mMatch env (unLoc v)
#endif
      go _                  = const [] -- TODO

#if __GLASGOW_HASKELL__ < 806
-- Note [TyVar Quantifiers]
--
-- GHC parses a tycon app as a list of types (Maybe Int becomes [Maybe, Int]).
-- A nullary tycon app becomes a singleton list, and a tyvar is treated as a
-- a nullary tycon. Quantifiers are tyvars, so they'll be rigidly buried in
-- singleton lists, meaning 'a' cannot match with 'Maybe Int' because [a]
-- will not unify with [Maybe, Int]. Singleton tycons suffer the same problem.
-- [Foo] will not match with [Maybe, Foo] when unfolding Foo. To solve this,
-- we 'look through' such singleton lists.

tyLookThrough :: Key TyMap -> Key TyMap
tyLookThrough (L _ (HsAppsTy [L _ (HsAppPrefix ty)])) = ty
tyLookThrough ty = ty

------------------------------------------------------------------------

data AppTyMap a
  = AppTyEmpty
  | ATM { atmAppInfix :: VMap a
        , atmAppPrefix :: TyMap a
        }
  deriving (Functor)

emptyAppTyMapWrapper :: AppTyMap a
emptyAppTyMapWrapper = ATM mEmpty mEmpty

instance PatternMap AppTyMap where
  type Key AppTyMap = LHsAppType GhcPs

  mEmpty :: AppTyMap a
  mEmpty = AppTyEmpty

  mUnion :: AppTyMap a -> AppTyMap a -> AppTyMap a
  mUnion AppTyEmpty m = m
  mUnion m AppTyEmpty = m
  mUnion m1 m2 = ATM
    { atmAppInfix = unionOn atmAppInfix m1 m2
    , atmAppPrefix = unionOn atmAppPrefix m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key AppTyMap -> A a -> AppTyMap a -> AppTyMap a
  mAlter env vs aty f AppTyEmpty = mAlter env vs aty f emptyAppTyMapWrapper
  mAlter env vs aty f m@(ATM {}) = go (unLoc aty)
    where
      go (HsAppInfix r) = m { atmAppInfix = mAlter env vs (unLoc r) f (atmAppInfix m) }
      go (HsAppPrefix ty) = m { atmAppPrefix = mAlter env vs ty f (atmAppPrefix m) }

  mMatch :: MatchEnv -> Key AppTyMap -> (Substitution, AppTyMap a) -> [(Substitution, a)]
  mMatch _   _  (_,AppTyEmpty) = []
  mMatch env aty (hs,m@ATM{})  = go (unLoc aty) (hs,m)
    where
      go (HsAppInfix r)   = mapFor atmAppInfix >=> mMatch env (unLoc r)
      go (HsAppPrefix ty) = mapFor atmAppPrefix >=> mMatch env ty
#endif

------------------------------------------------------------------------

newtype RFMap a = RFM { rfmField :: VMap (EMap a) }
  deriving (Functor)

instance PatternMap RFMap where
  type Key RFMap = LHsRecField' RdrName (LHsExpr GhcPs)

  mEmpty :: RFMap a
  mEmpty = RFM mEmpty

  mUnion :: RFMap a -> RFMap a -> RFMap a
  mUnion (RFM m1) (RFM m2) = RFM (mUnion m1 m2)

  mAlter :: AlphaEnv -> Quantifiers -> Key RFMap -> A a -> RFMap a -> RFMap a
  mAlter env vs lf f m = go (unLoc lf)
    where
      go (HsRecField lbl arg _pun) =
        m { rfmField = mAlter env vs (unLoc lbl) (toA (mAlter env vs arg f)) (rfmField m) }

  mMatch :: MatchEnv -> Key RFMap -> (Substitution, RFMap a) -> [(Substitution, a)]
  mMatch env lf (hs,m) = go (unLoc lf) (hs,m)
    where
      go (HsRecField lbl arg _pun) =
        mapFor rfmField >=> mMatch env (unLoc lbl) >=> mMatch env arg

-- Helper class to collapse the complex encoding of record fields into RdrNames.
-- (The complexity is to support punning/duplicate/overlapping fields, which
-- all happens well after parsing, so is not needed here.)
class RecordFieldToRdrName f where
  recordFieldToRdrName :: f -> RdrName

#if __GLASGOW_HASKELL__ < 806
instance RecordFieldToRdrName (AmbiguousFieldOcc p) where
#else
instance RecordFieldToRdrName (AmbiguousFieldOcc GhcPs) where
#endif
  recordFieldToRdrName = rdrNameAmbiguousFieldOcc

instance RecordFieldToRdrName (FieldOcc p) where
  recordFieldToRdrName = unLoc . rdrNameFieldOcc

fieldsToRdrNames
  :: RecordFieldToRdrName f
  => [LHsRecField' f arg]
  -> [LHsRecField' RdrName arg]
fieldsToRdrNames = map go
  where
    go (L l (HsRecField (L l2 f) arg pun)) =
      L l (HsRecField (L l2 (recordFieldToRdrName f)) arg pun)

------------------------------------------------------------------------

data TupleSortMap a = TupleSortMap
  { tsUnboxed :: MaybeMap a
  , tsBoxed :: MaybeMap a
  , tsConstraint :: MaybeMap a
  , tsBoxedOrConstraint :: MaybeMap a
  }
  deriving (Functor)

instance PatternMap TupleSortMap where
  type Key TupleSortMap = HsTupleSort

  mEmpty :: TupleSortMap a
  mEmpty = TupleSortMap mEmpty mEmpty mEmpty mEmpty

  mUnion :: TupleSortMap a -> TupleSortMap a -> TupleSortMap a
  mUnion m1 m2 = TupleSortMap
    { tsUnboxed = unionOn tsUnboxed m1 m2
    , tsBoxed = unionOn tsBoxed m1 m2
    , tsConstraint = unionOn tsConstraint m1 m2
    , tsBoxedOrConstraint = unionOn tsBoxedOrConstraint m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key TupleSortMap -> A a -> TupleSortMap a -> TupleSortMap a
  mAlter env vs HsUnboxedTuple f m =
    m { tsUnboxed = mAlter env vs () f (tsUnboxed m) }
  mAlter env vs HsBoxedTuple f m =
    m { tsBoxed = mAlter env vs () f (tsBoxed m) }
  mAlter env vs HsConstraintTuple f m =
    m { tsConstraint = mAlter env vs () f (tsConstraint m) }
  mAlter env vs HsBoxedOrConstraintTuple f m =
    m { tsBoxedOrConstraint = mAlter env vs () f (tsBoxedOrConstraint m) }

  mMatch :: MatchEnv -> Key TupleSortMap -> (Substitution, TupleSortMap a) -> [(Substitution, a)]
  mMatch env HsUnboxedTuple = mapFor tsUnboxed >=> mMatch env ()
  mMatch env HsBoxedTuple = mapFor tsBoxed >=> mMatch env ()
  mMatch env HsConstraintTuple = mapFor tsConstraint >=> mMatch env ()
  mMatch env HsBoxedOrConstraintTuple = mapFor tsBoxedOrConstraint >=> mMatch env ()

------------------------------------------------------------------------

-- Note [Telescope]
-- Haskell's forall quantification is a telescope (type binders are in-scope
-- to binders to the right. Example: forall r (a :: TYPE r). ...
--
-- To support this, we peel off the binders one at a time, extending the
-- environment at each layer.

data ForAllTyMap a = ForAllTyMap
  { fatNil :: TyMap a
  , fatUser :: ForAllTyMap a
  , fatKinded :: TyMap (ForAllTyMap a)
  }
  deriving (Functor)

instance PatternMap ForAllTyMap where
  type Key ForAllTyMap = ([LHsTyVarBndr GhcPs], LHsType GhcPs)

  mEmpty :: ForAllTyMap a
  mEmpty = ForAllTyMap mEmpty mEmpty mEmpty

  mUnion :: ForAllTyMap a -> ForAllTyMap a -> ForAllTyMap a
  mUnion m1 m2 = ForAllTyMap
    { fatNil = unionOn fatNil m1 m2
    , fatUser = unionOn fatUser m1 m2
    , fatKinded = unionOn fatKinded m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key ForAllTyMap -> A a -> ForAllTyMap a -> ForAllTyMap a
  mAlter env vs ([], ty) f m = m { fatNil = mAlter env vs ty f (fatNil m) }
#if __GLASGOW_HASKELL__ < 806
  mAlter env vs (L _ (UserTyVar (L _ v)):rest, ty) f m =
#else
  mAlter env vs (L _ (UserTyVar _ (L _ v)):rest, ty) f m =
#endif
    let
      env' = extendAlphaEnvInternal v env
      vs' = vs `exceptQ` [v]
    in m { fatUser = mAlter env' vs' (rest, ty) f (fatUser m) }
#if __GLASGOW_HASKELL__ < 806
  mAlter env vs (L _ (KindedTyVar (L _ v) k):rest, ty) f m =
#else
  mAlter _ _ (L _ (XTyVarBndr _):_,_) _ _ = missingSyntax "XTyVarBndr"
  mAlter env vs (L _ (KindedTyVar _ (L _ v) k):rest, ty) f m =
#endif
    let
      env' = extendAlphaEnvInternal v env
      vs' = vs `exceptQ` [v]
    in m { fatKinded  = mAlter env vs k (toA (mAlter env' vs' (rest, ty) f)) (fatKinded m) }

  mMatch :: MatchEnv -> Key ForAllTyMap -> (Substitution, ForAllTyMap a) -> [(Substitution, a)]
  mMatch env ([],ty) = mapFor fatNil >=> mMatch env ty
#if __GLASGOW_HASKELL__ < 806
  mMatch env (L _ (UserTyVar (L _ v)):rest, ty) =
#else
  mMatch env (L _ (UserTyVar _ (L _ v)):rest, ty) =
#endif
    let env' = extendMatchEnv env [v]
    in mapFor fatUser >=> mMatch env' (rest, ty)
#if __GLASGOW_HASKELL__ < 806
  mMatch env (L _ (KindedTyVar (L _ v) k):rest, ty) =
#else
  mMatch _ (L _ (XTyVarBndr _):_,_) = const []
  mMatch env (L _ (KindedTyVar _ (L _ v) k):rest, ty) =
#endif
    let env' = extendMatchEnv env [v]
    in mapFor fatKinded >=> mMatch env k >=> mMatch env' (rest, ty)

#if __GLASGOW_HASKELL__ < 810
#else
data ForallVisMap a = ForallVisMap
  { favVis :: MaybeMap a
  , favInvis :: MaybeMap a
  }
  deriving (Functor)

instance PatternMap ForallVisMap where
  type Key ForallVisMap = ForallVisFlag

  mEmpty :: ForallVisMap a
  mEmpty = ForallVisMap mEmpty mEmpty

  mUnion :: ForallVisMap a -> ForallVisMap a -> ForallVisMap a
  mUnion m1 m2 = ForallVisMap
    { favVis = unionOn favVis m1 m2
    , favInvis = unionOn favInvis m1 m2
    }

  mAlter :: AlphaEnv -> Quantifiers -> Key ForallVisMap -> A a -> ForallVisMap a -> ForallVisMap a
  mAlter env vs ForallVis f m =
    m { favVis = mAlter env vs () f (favVis m) }
  mAlter env vs ForallInvis f m =
    m { favInvis = mAlter env vs () f (favInvis m) }

  mMatch :: MatchEnv -> Key ForallVisMap -> (Substitution, ForallVisMap a) -> [(Substitution, a)]
  mMatch env ForallVis = mapFor favVis >=> mMatch env ()
  mMatch env ForallInvis = mapFor favInvis >=> mMatch env ()
#endif