packages feed

cryptol-3.6.0: src/Cryptol/ModuleSystem/Base.hs

-- |
-- Module      :  Cryptol.ModuleSystem.Base
-- Copyright   :  (c) 2013-2016 Galois, Inc.
-- License     :  BSD3
-- Maintainer  :  cryptol@galois.com
-- Stability   :  provisional
-- Portability :  portable
--
-- This is the main driver---it provides entry points for the
-- various passes.

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE ImplicitParams #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# Language DisambiguateRecordFields #-}
{-# LANGUAGE BlockArguments #-}

module Cryptol.ModuleSystem.Base where

import qualified Control.Exception as X
import Control.Monad (unless,when,forM)
import Data.Set(Set)
import qualified Data.Set as Set
import Data.Maybe (fromMaybe, maybeToList)
import Data.List(sortBy)
import qualified Data.List.NonEmpty as NE
import Data.List.NonEmpty (NonEmpty(..))
import Data.Function(on)
import Data.Monoid ((<>),Endo(..), Any(..))
import qualified Data.Text as T
import Data.Text.Encoding (decodeUtf8')
import System.Directory
  (doesFileExist, canonicalizePath, makeRelativeToCurrentDirectory)
import System.FilePath ( addExtension
                       , dropExtension
                       , isAbsolute
                       , joinPath
                       , splitDirectories
                       , (</>)
                       , normalise
                       , takeFileName
                       )
import qualified System.IO.Error as IOE
import qualified Data.Map as Map
import qualified Data.Map.Strict as MapS

import Prelude ()
import Prelude.Compat hiding ( (<>) )



import Cryptol.ModuleSystem.Fingerprint
import Cryptol.ModuleSystem.Interface
import Cryptol.ModuleSystem.Monad
import Cryptol.ModuleSystem.Name (Name,liftSupply,PrimMap,ModPath(..),nameIdent)
import Cryptol.ModuleSystem.Env ( DynamicEnv(..),FileInfo(..),fileInfo
                                , lookupModule
                                , lookupTCEntity
                                , LoadedModuleG(..), lmInterface
                                , meCoreLint, CoreLint(..)
                                , ModContext(..), ModContextParams(..)
                                , ModulePath(..), modulePathLabel
                                , EvalForeignPolicy (..)
                                , PassName(..), DebugOpts(..))
import           Cryptol.Eval.FFI.ForeignSrc
import qualified Cryptol.Eval                 as E
import qualified Cryptol.Eval.Concrete as Concrete
import           Cryptol.Eval.Concrete (Concrete(..))
import           Cryptol.Eval.FFI
import qualified Cryptol.ModuleSystem.NamingEnv as R
import qualified Cryptol.ModuleSystem.Renamer as R
import qualified Cryptol.Parser               as P
import qualified Cryptol.Parser.Unlit         as P
import Cryptol.Parser.AST as P
import Cryptol.Parser.NoPat (RemovePatterns(removePatterns))
import qualified Cryptol.Parser.ExpandPropGuards as ExpandPropGuards
  ( expandPropGuards, expandTopDecl, runExpandPropGuardsM )
import Cryptol.Parser.NoInclude (removeIncludesModule)
import Cryptol.Parser.Position (HasLoc(..), Range, emptyRange)
import qualified Cryptol.TypeCheck     as T
import qualified Cryptol.TypeCheck.AST as T
import qualified Cryptol.TypeCheck.PP as T
import qualified Cryptol.TypeCheck.Sanity as TcSanity
import qualified Cryptol.Eval.FFI.Error as FFI

import Cryptol.Utils.Ident ( preludeName, floatName, arrayName, suiteBName, primeECName
                           , preludeReferenceName, interactiveName, modNameChunks
                           , modNameIsNormal
                           , modNamesMatch, Namespace(NSModule) )
import Cryptol.Utils.PP (pretty, pp, hang, vcat, ($$), (<+>), (<.>), colon)
import Cryptol.Utils.Panic (panic)
import Cryptol.Utils.Logger(logPutStrLn, logPrint)
import Cryptol.Utils.Benchmark

import Cryptol.Prelude ( preludeContents, floatContents, arrayContents
                       , suiteBContents, primeECContents, preludeReferenceContents )
import Cryptol.Transform.MonoValues (rewModule)


-- Renaming --------------------------------------------------------------------

rename :: ModName -> R.NamingEnv -> R.RenameM a -> ModuleM a
rename modName env m = do
  ifaces <- getIfaces
  (res,ws) <- liftSupply $ \ supply ->
    let info = R.RenamerInfo
                 { renSupply  = supply
                 , renContext = TopModule modName
                 , renEnv     = env
                 , renIfaces  = ifaces
                 }
    in
    case R.runRenamer info m of
      (Right (a,supply'),ws) -> ((Right a,ws),supply')
      (Left errs,ws)         -> ((Left errs,ws),supply)

  renamerWarnings ws
  case res of
    Right r   -> return r
    Left errs -> renamerErrors errs

-- | Rename a module in the context of its imported modules.
renameModule :: P.Module PName -> ModuleM R.RenamedModule
renameModule m = rename (thing (mName m)) mempty (R.renameModule m)

renameImpNameInCurrentEnv :: P.ImpName PName -> ModuleM (P.ImpName Name)
renameImpNameInCurrentEnv (P.ImpTop top) =
 do ok <- isLoaded top
    if ok then
      pure (P.ImpTop top)
    else
      fail ("Top-level module not loaded: " ++ show (pp top))
renameImpNameInCurrentEnv (P.ImpNested pname) =
 do env <- getFocusedEnv
    case R.lookupListNS NSModule pname (mctxNames env) of
      [] -> do
        fail ("Undefined submodule name: " ++ show (pp pname))
      _:_:_ -> do
        fail ("Ambiguous submodule name: " ++ show (pp pname))
      [n] -> pure (P.ImpNested n)

-- NoPat -----------------------------------------------------------------------

-- | Run the noPat pass.
noPat :: RemovePatterns a => a -> ModuleM a
noPat a = do
  let (a',errs) = removePatterns a
  unless (null errs) (noPatErrors errs)
  return a'

-- ExpandPropGuards ------------------------------------------------------------

-- | Run the expandPropGuards pass.
expandPropGuards :: Module PName -> ModuleM (Module PName)
expandPropGuards a =
  case ExpandPropGuards.runExpandPropGuardsM $ ExpandPropGuards.expandPropGuards a of
    Left err -> expandPropGuardsError err
    Right a' -> pure a'

-- | Run the expandPropGuards pass on a group of top-level declarations.
expandPropGuardsDecls :: [P.TopDecl PName] -> ModuleM [P.TopDecl PName]
expandPropGuardsDecls ds =
  case ExpandPropGuards.runExpandPropGuardsM $
         concat <$> mapM ExpandPropGuards.expandTopDecl ds of
    Left err  -> expandPropGuardsError err
    Right ds' -> pure ds'

-- Parsing ---------------------------------------------------------------------

-- | Parse a module and expand includes
-- Returns a fingerprint of the module, and a set of dependencies due
-- to `include` directives.
parseModule ::
  ModulePath ->
  ModuleM (Fingerprint, MapS.Map FilePath Fingerprint, [P.Module PName])
parseModule path = do
  getBytes <- getByteReader

  bytesRes <- case path of
                InFile p -> io (X.try (getBytes p))
                InMem _ bs -> pure (Right bs)

  bytes <- case bytesRes of
    Right bytes -> return bytes
    Left exn ->
      case path of
        InFile p
          | IOE.isDoesNotExistError exn -> cantFindFile p
          | otherwise                   -> otherIOError p exn
        InMem p _ -> panic "parseModule"
                       [ "IOError for in-memory contents???"
                       , "Label: " ++ show p
                       , "Exception: " ++ show exn ]

  let fp = fingerprint bytes
  txt <- case decodeUtf8' bytes of
    Right txt -> return $! T.replace "\r\n" "\n" txt
    Left e    -> badUtf8 path fp e


  let cfg = P.defaultConfig
              { P.cfgSource  = case path of
                                 InFile p -> p
                                 InMem l _ -> l
              , P.cfgPreProc = P.guessPreProc (modulePathLabel path)
              }

  case P.parseModule cfg txt of
    Right pms ->
      do (pm1,deps) <-
           case path of
             InFile p ->
               do r <- getByteReader
                  (mo,d) <- unzip <$>
                    forM pms \pm ->
                    do mb <- io (removeIncludesModule r p pm)
                       case mb of
                         Right ok -> pure ok
                         Left err -> noIncludeErrors err
                  pure (mo, MapS.unions d)

             {- We don't do "include" resolution for in-memory files
                because at the moment the include resolution pass requires
                the path to the file to be known---this is used when
                looking for other inlcude files.  This could be
                generalized, but we can do it once we have a concrete use
                case as it would help guide the design. -}
             InMem {} -> pure (pms, MapS.empty)

{-
         case path of
           InFile {} -> io $ print (T.vcat (map T.pp pm1))
           InMem {} -> pure ()
--}
         fp `seq` return (fp, deps, pm1)

    Left err -> moduleParseError path fp err


-- Top Level Modules and Signatures --------------------------------------------


-- | Load a module by its path.
loadModuleByPath ::
  Bool {- ^ evaluate declarations in the module -} ->
  FilePath -> ModuleM T.TCTopEntity
loadModuleByPath eval path = do
  foundPath <- findFile path
  (fp, deps, pms) <- parseModule (InFile foundPath)
  extras <- case [ thing (P.mName pm) | pm <- pms
                                      , modNameIsNormal (thing (P.mName pm)) ] of
              n : _ -> checkPathLayout foundPath n
              []    -> pure Nothing
  withPrependedSearchPath (maybeToList extras) $
    last <$>
    forM pms \pm ->
    do let n = thing (P.mName pm)

       -- Check whether this module name has already been loaded from a
       -- different file
       env <- getModuleEnv
       -- path' is the resolved, absolute path, used only for checking
       -- whether it's already been loaded
       path' <- io (canonicalizePath foundPath)

       case lookupTCEntity n env of
         -- loadModule will calculate the canonical path again
         Nothing ->
           loadModuleAndDeps eval False
             (FromModule n) (InFile foundPath) fp deps pm
         Just lm
          | path' == loaded -> return (lmData lm)
          | otherwise       -> duplicateModuleName n path' loaded
          where loaded = lmModuleId lm

{- | Check whether the file's parent directories match the parent chunks of
the module's hierarchical name.  If they do, return the directory prefix
that should be prepended to the search path so that imports from a sibling
can be resolved.  If they do not, emit a warning and return 'Nothing',
leaving the search path alone. -}
checkPathLayout :: FilePath -> ModName -> ModuleM (Maybe FilePath)
checkPathLayout fp mname =
  case stripParentSuffix (modNameChunks mname)
                         (splitDirectories (dropExtension (normalise fp))) of
    Just root -> pure (Just (if null root then "." else root))
    Nothing   ->
      do relFp <- io (makeRelativeToCurrentDirectory fp)
         withLogger logPutStrLn $
           "[warning] " ++ show relFp ++ " does not match module name " ++
           pretty mname
         pure Nothing
  where
  stripParentSuffix chunks p =
    case (reverse chunks, reverse p) of
      -- We just check parent directories, not file name.
      (_ : rcs, _ : rds) -> go rds rcs
      _                  -> Nothing
    where
    go rest cs =
      case cs of
        []     -> Just (joinPath (reverse rest))
        c : cs' ->
          case rest of
            d : ds | d == c -> go ds cs'
            _               -> Nothing


-- | Load a module, unless it was previously loaded.
loadModuleFrom ::
  Bool {- ^ quiet mode -} -> ImportSource -> ModuleM (ModulePath,T.TCTopEntity)
loadModuleFrom quiet isrc =
  do let n = importedModule isrc
     mb <- getLoadedMaybe n
     case mb of
       Just m -> return (lmFilePath m, lmData m)
       Nothing ->
         do path <- findModule isrc n
            errorInFile path $
              do (fp, deps, pms) <- parseModule path
                 ms <- mapM (loadModuleAndDeps True quiet isrc path fp deps) pms
                 return (path,last ms)

-- | Load dependencies, typecheck, and add to the eval environment.
loadModuleAndDeps ::
  Bool {- ^ evaluate declarations in the module -} ->
  Bool {- ^ quiet mode: true suppresses the "loading module" message -} ->
  ImportSource ->
  ModulePath ->
  Fingerprint ->
  MapS.Map FilePath Fingerprint {- ^ `include` dependencies -} ->
  P.Module PName ->
  ModuleM T.TCTopEntity
loadModuleAndDeps eval quiet isrc path fp incDeps pm0 =
  loading isrc $
  do let pm = addPrelude pm0
     impDeps <- loadDeps pm
     fst <$> doLoadModule eval quiet isrc path fp incDeps pm impDeps

-- | Typecheck and add to the eval environment.
doLoadModule ::
  Bool {- ^ evaluate declarations in the module -} ->
  Bool {- ^ quiet mode: true suppresses the "loading module" message -} ->
  ImportSource ->
  ModulePath ->
  Fingerprint ->
  MapS.Map FilePath Fingerprint {- ^ `include` dependencies -} ->
  P.Module PName ->
  Set ModName ->
  ModuleM (T.TCTopEntity, FileInfo)
doLoadModule eval quiet isrc path fp incDeps pm impDeps =
  do let what = case P.mDef pm of
                  P.InterfaceModule {} -> "interface module"
                  _                    -> "module"

     unless quiet $ withLogger logPutStrLn
       ("Loading " ++ what ++ " " ++ pretty (P.thing (P.mName pm)))


     (nameEnv,tcm,renMod) <- checkModule isrc pm

     -- extend the eval env, unless a functor.
     tbl <- Concrete.primTable <$> getEvalOptsAction
     let ?evalPrim = \i -> Right <$> Map.lookup i tbl
     callStacks <- getCallStacks
     let ?callStacks = callStacks
     let shouldEval =
           case tcm of
             T.TCTopModule m | eval && not (T.isParametrizedModule m) -> Just m
             _ -> Nothing

     foreignSrc <- case shouldEval of
                      Just m ->
                        do fsrc <- evalForeign m
                           modifyEvalEnv (E.moduleEnv Concrete m)
                           pure fsrc
                      Nothing -> pure Nothing

     let fi = fileInfo fp incDeps impDeps foreignSrc
     saveRen <- getSaveRenamed
     loadedModule path fi nameEnv foreignSrc tcm $!
       if saveRen then Just renMod else Nothing

     return (tcm, fi)

  where
  evalForeign tcm
    | not (null foreignFs) =
      ffiLoadErrors (T.mName tcm) (map FFI.FFIInFunctor foreignFs)
    | not (null dups) =
      ffiLoadErrors (T.mName tcm) (map FFI.FFIDuplicates dups)
    | null foreigns = pure Nothing
    | otherwise =
      getEvalForeignPolicy >>= \case
        AlwaysEvalForeign -> doEvalForeign (ffiLoadErrors (T.mName tcm))
        PreferEvalForeign -> doEvalForeign \errs ->
          withLogger logPrint $
            hang
              ("[warning] Could not load all foreign implementations for module"
                <+> pp (T.mName tcm) <.> colon) 4 $
              vcat (map pp errs)
              $$ "Fallback cryptol implementations will be used if available"
        NeverEvalForeign -> pure Nothing

    where foreigns  = findForeignDecls tcm
          foreignFs = T.findForeignDeclsInFunctors tcm
          dups      = [ d | d@(_ :| _ : _) <- NE.groupBy ((==) `on` nameIdent)
                                            $ sortBy (compare `on` nameIdent)
                                            $ map fst foreigns ]
          doEvalForeign handleErrs =
            case path of
              InFile p -> io (loadForeignSrc p) >>=
                \case

                  Right fsrc -> do
                    unless quiet $
                      case getForeignSrcPath fsrc of
                        Just fpath -> withLogger logPutStrLn $
                          "Loading dynamic library " ++ takeFileName fpath
                        Nothing -> pure ()
                    (errs, ()) <-
                      modifyEvalEnvM (evalForeignDecls fsrc foreigns)
                    unless (null errs) $
                      handleErrs errs
                    pure $ Just fsrc

                  Left err -> do
                    handleErrs [err]
                    pure Nothing

              InMem m _ -> panic "doLoadModule"
                ["Can't find foreign source of in-memory module", m]

-- | Rewrite an import declaration to be of the form:
--
-- > import foo as foo [ [hiding] (a,b,c) ]
fullyQualified :: P.Import -> P.Import
fullyQualified i = i { iAs = Just (thing (iModule i)) }

moduleFile :: ModName -> String -> FilePath
moduleFile n = addExtension (joinPath (modNameChunks n))


-- | Discover a module.
findModule :: ImportSource -> ModName -> ModuleM ModulePath
findModule isrc n = do
  paths <- getSearchPath
  loop (possibleFiles paths)
  where
  loop paths = case paths of

    path:rest -> do
      b <- io (doesFileExist path)
      if b then return (InFile path) else loop rest

    [] -> handleNotFound

  handleNotFound =
    case n of
      m | m == preludeName -> pure (InMem "Cryptol" preludeContents)
        | m == floatName   -> pure (InMem "Float" floatContents)
        | m == arrayName   -> pure (InMem "Array" arrayContents)
        | m == suiteBName  -> pure (InMem "SuiteB" suiteBContents)
        | m == primeECName -> pure (InMem "PrimeEC" primeECContents)
        | m == preludeReferenceName -> pure (InMem "Cryptol::Reference" preludeReferenceContents)
      _ -> moduleNotFound isrc n =<< getSearchPath

  -- generate all possible search paths
  possibleFiles paths = do
    path <- paths
    ext  <- P.knownExts
    return (path </> moduleFile n ext)

-- | Discover a file. This is distinct from 'findModule' in that we
-- assume we've already been given a particular file name.
findFile :: FilePath -> ModuleM FilePath
findFile path
  | isAbsolute path =
    do -- No search path checking for absolute paths
       b <- io (doesFileExist path)
       if b then return path else cantFindFile path
  | otherwise =
    do paths <- getSearchPath
       loop (possibleFiles paths)
       where
       loop paths = case paths of
                      path' : rest ->
                        do b <- io (doesFileExist path')
                           if b then return (normalise path') else loop rest
                      [] -> cantFindFile path
       possibleFiles paths = map (</> path) paths

-- | Add the prelude to the import list if it's not already mentioned.
addPrelude :: P.Module PName -> P.Module PName
addPrelude m
  | preludeName == P.thing (P.mName m) = m
  | preludeName `elem` importedMods    = m
  | otherwise                          = m { mDef = newDef }
  where
  newDef =
    case mDef m of
      NormalModule ds -> NormalModule (P.DImport prel : ds)
      FunctorInstance f as ins k -> FunctorInstance f as ins k
      InterfaceModule s -> InterfaceModule s { sigImports = P.SigImport prel
                                             : sigImports s }
      ModuleAlias t -> ModuleAlias t

  importedMods  = map (P.thing . P.iModule . P.thing) (P.mImports m)
  prel = P.Located
    { P.srcRange = emptyRange
    , P.thing    = P.Import
      { iModule  = Located emptyRange (P.ImpTop preludeName)
      , iAs      = Nothing
      , iSpec    = Nothing
      , iInst    = Nothing
      , iDoc     = Nothing
      }
    }

-- | Load the dependencies of a module into the environment.
loadDeps :: P.ModuleG mname name -> ModuleM (Set ModName)
loadDeps m =
  do let ds = findDeps m
     mapM_ (loadModuleFrom False) ds
     pure (Set.fromList (map importedModule ds))

-- | Find all imports in a module.
findDeps :: P.ModuleG mname name -> [ImportSource]
findDeps m = appEndo (snd (findDeps' m)) []

findDepsOfModule :: ModName -> ModuleM (ModulePath, FileInfo)
findDepsOfModule m =
  do mpath <- findModule (FromModule m) m
     findDepsOf mpath

findDepsOf :: ModulePath -> ModuleM (ModulePath, FileInfo)
findDepsOf mpath' =
  do mpath <- case mpath' of
                InFile file -> InFile <$> io (canonicalizePath file)
                InMem {}    -> pure mpath'
     (fi, _) <- parseWithDeps mpath
     pure (mpath, fi)

{- | Parse the given module path and find its dependencies.
The reason we return a list here, is that sometime files may
contain multiple modules (e.g., due to desugaring `parameter` blocks
in functors. -}
parseWithDeps ::
  ModulePath ->
  ModuleM (FileInfo, [(Module PName, [ImportSource])])
parseWithDeps mpath =
  do (fp, incs, ms) <- parseModule mpath
     let ms' = map addPrelude ms
         depss = map findDeps' ms'
     let (anyF,imps) = mconcat depss
     fpath <- if getAny anyF
                then do mb <- io case mpath of
                                   InFile path -> foreignLibPath path
                                   InMem {}    -> pure Nothing
                        pure case mb of
                               Nothing -> Map.empty
                               Just (fpath, exists) ->
                                 Map.singleton fpath exists
                else pure Map.empty
     pure
       ( FileInfo
           { fiFingerprint = fp
           , fiIncludeDeps = incs
           , fiImportDeps  = Set.fromList (map importedModule (appEndo imps []))
           , fiForeignDeps = fpath
           }
       , zip ms' $ map ((`appEndo` []) . snd) depss
       )

-- | Find the set of top-level modules imported by a module.
findModuleDeps :: P.ModuleG mname name -> Set P.ModName
findModuleDeps = Set.fromList . map importedModule . findDeps

-- | A helper `findDeps` and `findModuleDeps` that actually does the searching.
findDeps' :: P.ModuleG mname name -> (Any, Endo [ImportSource])
findDeps' m =
  case mDef m of
    NormalModule ds -> mconcat (map depsOfDecl ds)
    FunctorInstance f as _ _k ->
      let fds = loadImpName FromModuleInstance f
          ads = case as of
                  DefaultInstArg a -> loadInstArg a
                  DefaultInstAnonArg ds -> mconcat (map depsOfDecl ds)
                  NamedInstArgs args -> mconcat (map loadNamedInstArg args)
      in fds <> ads
    InterfaceModule s -> mconcat (map loadSigImp (sigImports s))
    ModuleAlias t -> loadImpName FromModuleAlias t
  where
  loadI i = (mempty, Endo (i:))

  loadImpName src l =
    case thing l of
      ImpTop f -> loadI (src l { thing = f })
      _        -> mempty

  loadImpD li = loadImpName (FromImport . new) (thing . iModule <$> li)
    where new i = i { thing = (thing li) { iModule = i } }

  loadIfaceParam mp = loadImpName FromSigImport (mpSignature mp)

  loadSigImp si =
    case si of
      P.SigImport li     -> loadImpD li
      P.SigIfaceImport mp -> loadIfaceParam mp

  loadNamedInstArg (ModuleInstanceNamedArg _ f) = loadInstArg f
  loadInstArg f =
    case thing f of
      ModuleArg mo -> loadImpName FromModuleInstance f { thing = mo }
      _            -> mempty

  depsOfDecl d =
    case d of
      DImport li -> loadImpD li

      DModule TopLevel { tlValue = NestedModule nm } -> findDeps' nm

      DModParam mo -> loadImpName FromSigImport s
        where s = mpSignature mo

      Decl dd -> depsOfDecl' (tlValue dd)

      _ -> mempty

  depsOfDecl' d =
    case d of
      DLocated d' _ -> depsOfDecl' d'
      DBind b ->
        case thing (bDef b) of
          DForeign {} -> (Any True, mempty)
          _ -> mempty
      _ -> mempty






-- Type Checking ---------------------------------------------------------------

-- | Typecheck a single expression, yielding a renamed parsed expression,
-- typechecked core expression, and a type schema.
checkExpr :: P.Expr PName -> ModuleM (P.Expr Name,T.Expr,T.Schema)
checkExpr e = do

  fe <- getFocusedEnv
  let params = mctxParams fe
      decls  = mctxDecls fe
      names  = mctxNames fe

  -- run NoPat
  npe <- noPat e

  -- rename the expression with dynamic names shadowing the opened environment
  re  <- rename interactiveName names (R.rename npe)

  -- merge the dynamic and opened environments for typechecking
  prims <- getPrimMap
  let act  = TCAction { tcAction = T.tcExpr, tcLinter = exprLinter
                      , tcPrims = prims }
  (te,s) <- typecheck act re params decls

  return (re,te,s)

-- | Typecheck a group of declarations.
--
-- INVARIANT: This assumes that NoPat has already been run on the declarations.
checkDecls :: [P.TopDecl PName] -> ModuleM (R.NamingEnv,[T.DeclGroup], Map.Map Name T.TySyn)
checkDecls ds = do
  fe <- getFocusedEnv
  let params = mctxParams fe
      decls  = mctxDecls  fe
      names  = mctxNames  fe

  epgds <- expandPropGuardsDecls ds

  (declsEnv,rds) <- rename interactiveName names
                  $ R.renameTopDecls epgds
  prims <- getPrimMap
  let act  = TCAction { tcAction = T.tcDecls, tcLinter = declsLinter
                      , tcPrims = prims }
  (ds',tyMap) <- typecheck act rds params decls
  return (declsEnv,ds',tyMap)

-- | Generate the primitive map. If the prelude is currently being loaded, this
-- should be generated directly from the naming environment given to the renamer
-- instead.
getPrimMap :: ModuleM PrimMap
getPrimMap  =
  do env <- getModuleEnv
     let mkPrims = ifacePrimMap . lmInterface
         mp `alsoPrimFrom` m =
           case lookupModule m env of
             Nothing -> mp
             Just lm -> mkPrims lm <> mp

     case lookupModule preludeName env of
       Just prel -> return $ mkPrims prel
                            `alsoPrimFrom` floatName
       Nothing -> panic "Cryptol.ModuleSystem.Base.getPrimMap"
                  [ "Unable to find the prelude" ]

-- | Typecheck a single module.
-- Note: we assume that @include@s have already been processed
checkModule ::
  ImportSource                      {- ^ why are we loading this -} ->
  P.Module PName                    {- ^ module to check -} ->
  ModuleM (R.NamingEnv,T.TCTopEntity, Module Name)
checkModule isrc m = do

  dbgOpts <- getDebugOpts
  let prelOk = dbgIncludePrelude dbgOpts || thing (mName m) /= preludeName
      setting = if dbgIncludePrelude dbgOpts then id else T.debugHidePreludeNames
      dump nm dbg =
        when (prelOk && (nm `Set.member` dbgDumpAfter dbgOpts))
          (io (print (setting (T.debugShowUniques dbg))))

  dump PassParser (pp m) 

  -- check that the name of the module matches expectations
  let nm = importedModule isrc
  unless (modNamesMatch nm (thing (P.mName m)))
         (moduleNameMismatch nm (mName m))

  -- remove pattern bindings
  npm <- noPat m
  dump PassNoPat (pp npm) 

  -- run expandPropGuards
  epgm <- expandPropGuards npm
  dump PassPropGuards (pp epgm)

  -- rename everything
  renMod <- renameModule epgm
  dump PassRename (pp renMod)

  -- when generating the prim map for the typechecker, if we're checking the
  -- prelude, we have to generate the map from the renaming environment, as we
  -- don't have the interface yet.
  prims <- if thing (mName m) == preludeName
              then return (R.toPrimMap (R.rmDefines renMod))
              else getPrimMap

  -- typecheck
  let act = TCAction { tcAction = T.tcModule
                     , tcLinter = tcTopEntitytLinter (P.thing (P.mName m))
                     , tcPrims  = prims }


  tcm <- typecheck act (R.rmModule renMod) NoParams (R.rmImported renMod)
  dump PassTC (pp tcm)

  rewMod <- case tcm of
              T.TCTopModule mo -> T.TCTopModule <$> liftSupply (`rewModule` mo)
              T.TCTopSignature {} -> pure tcm
  dump PassREW (pp tcm)

  let nameEnv = case tcm of
                  T.TCTopModule mo -> T.mInScope mo
                  -- Name env for signatures does not change after typechecking
                  T.TCTopSignature {} -> mInScope (R.rmModule renMod)
  pure (nameEnv,rewMod, R.rmModule renMod)

data TCLinter o = TCLinter
  { lintCheck ::
      o -> T.InferInput ->
                    Either (Range, TcSanity.Error) [TcSanity.ProofObligation]
  , lintModule :: Maybe P.ModName
  }


exprLinter :: TCLinter (T.Expr, T.Schema)
exprLinter = TCLinter
  { lintCheck = \(e',s) i ->
      case TcSanity.tcExpr i e' of
        Left err     -> Left err
        Right (s1,os)
          | TcSanity.SameIf os' <- TcSanity.same s s1 ->
                                        Right (map T.tMono os' ++ os)
          | otherwise -> Left ( fromMaybe emptyRange (getLoc e')
                              , TcSanity.TypeMismatch "exprLinter" s s1
                              )
  , lintModule = Nothing
  }

declsLinter :: TCLinter ([ T.DeclGroup ], a)
declsLinter = TCLinter
  { lintCheck = \(ds',_) i -> case TcSanity.tcDecls i ds' of
                                Left err -> Left err
                                Right os -> Right os

  , lintModule = Nothing
  }

moduleLinter :: P.ModName -> TCLinter T.Module
moduleLinter m = TCLinter
  { lintCheck   = \m' i -> case TcSanity.tcModule i m' of
                            Left err -> Left err
                            Right os -> Right os
  , lintModule  = Just m
  }

tcTopEntitytLinter :: P.ModName -> TCLinter T.TCTopEntity
tcTopEntitytLinter m = TCLinter
  { lintCheck   = \m' i -> case m' of
                             T.TCTopModule mo ->
                               lintCheck (moduleLinter m) mo i
                             T.TCTopSignature {} -> Right []
                                -- XXX: what can we lint about module interfaces
  , lintModule  = Just m
  }


type Act i o = i -> T.InferInput -> IO (T.InferOutput o)

data TCAction i o = TCAction
  { tcAction :: Act i o
  , tcLinter :: TCLinter o
  , tcPrims  :: PrimMap
  }

typecheck ::
  (Show i, Show o, HasLoc i) =>
  TCAction i o -> i -> ModContextParams -> IfaceDecls -> ModuleM o
typecheck act i params env = do

  let range = fromMaybe emptyRange (getLoc i)
  input <- genInferInput range (tcPrims act) params env
  out   <- io (tcAction act i input)

  case out of

    T.InferOK nameMap warns seeds supply' o ->
      do setNameSeeds seeds
         setSupply supply'
         typeCheckWarnings nameMap warns
         menv <- getModuleEnv
         case meCoreLint menv of
           NoCoreLint -> return ()
           CoreLint   -> case lintCheck (tcLinter act) o input of
                           Right as ->
                             let ppIt l = mapM_ (logPrint l . T.pp)
                             in withLogger ppIt (TcSanity.onlyNonTrivial as)
                           Left (loc,err) ->
                            panic "Core lint failed:"
                              [ "Location: " ++ show (T.pp loc)
                              , show (T.pp err)
                              ]
         return o

    T.InferFailed nameMap warns errs ->
      do typeCheckWarnings nameMap warns
         typeCheckingFailed nameMap errs

-- | Generate input for the typechecker.
genInferInput :: Range -> PrimMap -> ModContextParams -> IfaceDecls ->
                                                          ModuleM T.InferInput
genInferInput r prims params env = do
  seeds <- getNameSeeds
  monoBinds <- getMonoBinds
  solver <- getTCSolver
  supply <- getSupply
  searchPath <- getSearchPath
  callStacks <- getCallStacks

  topMods <- getAllLoaded
  topSigs <- getAllLoadedSignatures

  return T.InferInput
    { T.inpRange            = r
    , T.inpVars             = Map.map ifDeclSig (ifDecls env)
    , T.inpTSyns            = ifTySyns env
    , T.inpNominalTypes     = ifNominalTypes env
    , T.inpSignatures       = ifSignatures env
    , T.inpNameSeeds        = seeds
    , T.inpMonoBinds        = monoBinds
    , T.inpCallStacks       = callStacks
    , T.inpSearchPath       = searchPath
    , T.inpSupply           = supply
    , T.inpParams           = case params of
                                NoParams -> T.allParamNames mempty
                                FunctorParams ps -> T.allParamNames ps
                                InterfaceParams ps -> ps
    , T.inpPrimNames        = prims
    , T.inpSolver           = solver
    , T.inpTopModules       = topMods
    , T.inpTopSignatures    = topSigs
    }


-- Evaluation ------------------------------------------------------------------

evalExpr :: T.Expr -> ModuleM Concrete.Value
evalExpr e = do
  env <- getEvalEnv
  denv <- getDynEnv
  evopts <- getEvalOptsAction
  let tbl = Concrete.primTable evopts
  let ?evalPrim = \i -> Right <$> Map.lookup i tbl
  let ?range = emptyRange
  callStacks <- getCallStacks
  let ?callStacks = callStacks

  io $ E.runEval mempty (E.evalExpr Concrete (env <> deEnv denv) e)

benchmarkExpr :: Double -> T.Expr -> ModuleM BenchmarkStats
benchmarkExpr period e = do
  env <- getEvalEnv
  denv <- getDynEnv
  evopts <- getEvalOptsAction
  let env' = env <> deEnv denv
  let tbl = Concrete.primTable evopts
  let ?evalPrim = \i -> Right <$> Map.lookup i tbl
  let ?range = emptyRange
  callStacks <- getCallStacks
  let ?callStacks = callStacks

  let eval expr = E.runEval mempty $
        E.evalExpr Concrete env' expr >>= E.forceValue
  io $ benchmark period eval e

evalDecls :: [T.DeclGroup] -> ModuleM ()
evalDecls dgs = do
  env <- getEvalEnv
  denv <- getDynEnv
  evOpts <- getEvalOptsAction
  let env' = env <> deEnv denv
  let tbl = Concrete.primTable evOpts
  let ?evalPrim = \i -> Right <$> Map.lookup i tbl
  callStacks <- getCallStacks
  let ?callStacks = callStacks

  deEnv' <- io $ E.runEval mempty (E.evalDecls Concrete dgs env')
  let denv' = denv { deDecls = deDecls denv ++ dgs
                   , deEnv = deEnv'
                   }
  setDynEnv denv'