{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}

module Clash.Tests.Core.FreeVars (tests) where

#if MIN_VERSION_ghc(9,0,0)
import           GHC.Types.SrcLoc        (noSrcSpan)
#else
import           SrcLoc                  (noSrcSpan)
#endif
import qualified Control.Lens            as Lens

import           Test.Tasty
import           Test.Tasty.HUnit

import           Clash.Core.FreeVars     (globalIds)
import           Clash.Core.Name         (Name(..), NameSort(..))
import           Clash.Core.Term         (Term(Var, App, Lam))
import           Clash.Core.Type         (ConstTy(..), Type(ConstTy))
import           Clash.Core.Var          (IdScope(..), Var(..))

-- TODO: We need tooling to create these mock constructs
fakeName :: Name a
fakeName =
  Name
    { nameSort=User
    , nameOcc="fake"
    , nameUniq=0
    , nameLoc=noSrcSpan
    }

f :: IdScope -> Var Term
f scope =
  let unique = 20 in
  Id { varName = Name { nameSort=User
                      , nameOcc="f"
                      , nameUniq=unique
                      , nameLoc=noSrcSpan }
     , varUniq = unique
     , varType = ConstTy (TyCon fakeName)
     , idScope = scope }

fLocalId, fGlobalId :: Var Term
fLocalId = f LocalId
fGlobalId = f GlobalId

-- 'term1' is a simple lambda function:
--
--   \f -> g f
--
-- where f and g have the same unique, but f has been marked as _local_ while
-- g is _global_. In other words:
--
--   \f[l] -> f[g] f[l]
--
-- This term is tested against to check whether various functions account for
-- the distinction between local/global variables correctly.
term1 :: Term
term1 =
  Lam fLocalId (Var fGlobalId `App` Var fLocalId)

tests :: TestTree
tests =
  let globs1 = Lens.toListOf globalIds term1 in
  testGroup
    "Clash.Tests.Core.FreeVars"
    [ testCase "globalIds1" $ globs1 @=? [fGlobalId]
    , testCase "globalIds2" $
        assertBool
          "Global and local id can't BOTH be in globs1"
          (fLocalId `notElem` globs1)
    ]