{- Language/Haskell/TH/Desugar/FV.hs

(c) Ryan Scott 2018

Compute free variables of programs.
-}

{-# LANGUAGE CPP #-}
module Language.Haskell.TH.Desugar.FV
  ( fvDType
  , extractBoundNamesDPat
  ) where

#if __GLASGOW_HASKELL__ < 804
import Data.Monoid ((<>))
#endif
import Language.Haskell.TH.Syntax
import Language.Haskell.TH.Desugar.AST
import qualified Language.Haskell.TH.Desugar.OSet as OS
import Language.Haskell.TH.Desugar.OSet (OSet)

-- | Compute the free variables of a 'DType'.
fvDType :: DType -> OSet Name
fvDType = go
  where
    go :: DType -> OSet Name
    go (DForallT tele ty)      = fv_dtele tele (go ty)
    go (DConstrainedT ctxt ty) = foldMap fvDType ctxt <> go ty
    go (DAppT t1 t2)           = go t1 <> go t2
    go (DAppKindT t k)         = go t <> go k
    go (DSigT ty ki)           = go ty <> go ki
    go (DVarT n)               = OS.singleton n
    go (DConT {})              = OS.empty
    go DArrowT                 = OS.empty
    go (DLitT {})              = OS.empty
    go DWildCardT              = OS.empty

-----
-- Extracting bound term names
-----

-- | Extract the term variables bound by a 'DPat'.
--
-- This does /not/ extract any type variables bound by pattern signatures,
-- constructor patterns, or type patterns.
extractBoundNamesDPat :: DPat -> OSet Name
extractBoundNamesDPat = go
  where
    go :: DPat -> OSet Name
    go (DLitP _)        = OS.empty
    go (DVarP n)        = OS.singleton n
    go (DConP _ _ pats) = foldMap go pats
    go (DTildeP p)      = go p
    go (DBangP p)       = go p
    go (DSigP p _)      = go p
    go DWildP           = OS.empty
    go (DTypeP _)       = OS.empty
    go (DInvisP _)      = OS.empty

-----
-- Binding forms
-----

-- | Adjust the free variables of something following a 'DForallTelescope'.
fv_dtele :: DForallTelescope -> OSet Name -> OSet Name
fv_dtele (DForallVis   tvbs) = fv_dtvbs tvbs
fv_dtele (DForallInvis tvbs) = fv_dtvbs tvbs

-- | Adjust the free variables of something following 'DTyVarBndr's.
fv_dtvbs :: [DTyVarBndr flag] -> OSet Name -> OSet Name
fv_dtvbs tvbs fvs = foldr fv_dtvb fvs tvbs

-- | Adjust the free variables of something following a 'DTyVarBndr'.
fv_dtvb :: DTyVarBndr flag -> OSet Name -> OSet Name
fv_dtvb (DPlainTV n _)    fvs = OS.delete n fvs
fv_dtvb (DKindedTV n _ k) fvs = OS.delete n fvs <> fvDType k
