packages feed

Sit-0.2017.2.26: src/TypeChecker.hs

{-# LANGUAGE CPP #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeSynonymInstances, FlexibleInstances, MultiParamTypeClasses #-}

module TypeChecker where

import Control.Applicative
import Control.Monad
import Control.Monad.Except
import Control.Monad.Identity
import Control.Monad.Reader
import Control.Monad.State

import Data.Functor
import Data.Maybe
import Data.Map (Map)
import qualified Data.Map as Map
import qualified Data.Set as Set

import Debug.Trace

import qualified Sit.Abs as A
import Sit.Print
import Sit.ErrM

import Abstract as A
import Internal
import Substitute
import Evaluation

import Lens
import Impossible
#include "undefined.h"

-- | Type errors are just strings.
type TypeError = String

-- | Local context

type Cxt = [ (Id, Dom VType) ]

data TCEnv = TCEnv
  { _envCxt :: Cxt  -- ^ Typing context.
  , _envEnv :: Env  -- ^ Default environment.
  }

makeLens ''TCEnv

-- | Global state

data TCSt = TCSt
  { _stTySigs :: Map Id VType
  , _stDefs   :: Map Id Val
  }

makeLens ''TCSt

-- | The type checking monad
type Check = ReaderT TCEnv (StateT TCSt (Except TypeError))

-- * Type checker

typeCheck :: [A.Decl] -> Either String ()
typeCheck decls = runExcept (evalStateT (runReaderT (checkDecls decls) initEnv) initSt)
  where
  initEnv = TCEnv { _envCxt   = []        , _envEnv = []        }
  initSt  = TCSt  { _stTySigs = Map.empty , _stDefs = Map.empty }

checkDecls :: [A.Decl] -> Check ()
checkDecls = mapM_ checkDecl

checkDecl :: A.Decl -> Check ()
checkDecl = \case
  A.Blank{} -> return ()
  A.Open{}  -> return ()
  A.Sig x a -> checkSig x a
  A.Def x e -> checkDef x e

-- | Check a type signature.

checkSig :: A.Ident -> A.Exp -> Check ()
checkSig x0@(A.Ident x) a = traceCheck (A.Sig x0 a) $ do

  -- Check that x is not already defined
  mt <- lookupTySig x
  unless (isNothing mt) $
    throwError $ "Duplicate type signature for " ++ x

  -- Check type and add to signature
  t <- checkType a
  -- traceM $ "Adding  " ++ show x ++ "  of type  " ++ show t
  addTySig x =<< evaluate t

-- | Check a definition.

checkDef :: A.Ident -> A.Exp -> Check ()
checkDef x0@(A.Ident x) e = traceCheck (A.Def x0 e) $ do

  -- Check that x has a signature
  let noSig = throwError $ "Missing type signature for " ++ x
  t <- maybe noSig return =<< lookupTySig x

  -- Check that x has not yet a definition
  mv <- lookupDef x
  unless (isNothing mv) $
    throwError $ "Duplicate definition of " ++ x

  -- Check definition and add to signature
  v <- checkExp e t
  -- traceM $ "Adding  " ++ show x ++ "  of value  " ++ show v
  addDef x =<< evaluate v

-- | Check well-formedness of a type.

checkType :: A.Exp -> Check Type
checkType e = fst <$> inferType e

-- | Check that something is a type and infer its universe level.

inferType :: A.Exp -> Check (Type, VLevel)
inferType e = do
  let invalidType = throwError $ "Not a valid type expression: " ++ printTree e
  case e of

    -- Size type (internal use only).

    -- Each universe is closed under size quantification.
    -- Thus, we place Size in Set0.

    A.Size -> return (Size, vsConst 0)

    -- Universes (shape irrelevant)

    A.Set  -> return (Type sZero, vsConst 1)
    A.Set1 -> return (Type $ sSuc sZero, vsConst 2)
    A.Set2 -> return (Type $ sSuc $ sSuc sZero, vsConst 3)
    A.App A.Set l -> do
      a <- resurrect ShapeIrr $ checkLevel l
      v <- evaluate a
      return (Type a, vsSuc v)

    -- Natural number type (shape irrelevant)

    A.App A.Nat s -> do
      a <- resurrect ShapeIrr $ checkSize s
      v <- evaluate a
      return (Nat a, vsZero)

    -- Function types

    A.Arrow a b -> do
      (u, l1) <- inferType a
      (t, l2) <- inferType b
      return (Pi (defaultDom u) (NoAbs "_" t) , maxSize l1 l2)

    A.Pi e a b -> do
      let failure = throwError $ "Expected list of identifiers, found " ++ printTree e
      xs <- maybe failure return $ parseIdUs e
      inferPisType (map (, defaultDom a) xs) $ inferType b

    A.Forall bs c -> inferPisType (fromBind =<< bs) $ inferType c
      where
      fromBind :: A.Bind -> [(A.IdU, Dom A.Exp)]
      fromBind = \case
        A.BIrrel x  -> return (A.Id x, Dom Irrelevant A.Size)
        A.BRel   x  -> return (A.Id x, Dom ShapeIrr   A.Size)
        A.BAnn xs a -> map (\ x -> (A.Id x, defaultDom a)) xs

    -- Neutral types

    e | A.introduction e -> invalidType

    e -> do
      (t,v) <- inferExp e
      case v of
        VType l -> return (t,l)
        _ -> invalidType

inferPisType :: [(A.IdU, Dom A.Exp)] -> Check (Type, VLevel) -> Check (Type, VLevel)
inferPisType = foldr (.) id . map (uncurry inferPiType)

inferPiType :: A.IdU -> Dom A.Exp -> Check (Type, VLevel) -> Check (Type, VLevel)
inferPiType x dom cont = do

  -- Check the domain
  (u, l1) <- inferType $ unDom dom

  -- Check the codomain in the extended context.
  v <- evaluate u
  addContext (x, v) $ do
    (t, l2) <- cont

    -- Compute the universe level of the Pi-type.
    let l0 = maxSize l1 l2

    -- Check that the level does not mention the bound variable
    -- If yes, return oo instead.
    l <- case fromMaybe __IMPOSSIBLE__ $ sizeView l0 of
      SVVar k' _ -> do
        k <- length <$> asks _envCxt
        return $ if k' >= k then VInfty else l0
      _ -> return l0

    -- Construct the function type
    return ( Pi (dom $> u) $ Abs (fromIdU x) t , l )

checkSize :: A.Exp -> Check Size
checkSize e = checkExp e VSize
-- checkSize = \case
--   A.Infty        -> return Infty
--   A.LZero        -> return $ sZero
--   A.App A.LSuc e -> sSuc <$> checkSize e
--   e@(A.Var x)    -> checkExp e VSize
--   e -> throwError $ "Not a valid size expression: " ++ printTree e

checkLevel :: A.Exp -> Check Level
checkLevel = \case
  A.LZero        -> return $ sZero
  A.App A.LSuc e -> sSuc <$> checkLevel e
  e@(A.Var x)    -> checkExp e VSize
  e -> throwError $ "Not a valid level expression: " ++ printTree e

-- maxLevel :: A.Exp -> VLevel -> VLevel -> Check VLevel
-- maxLevel e l1 l2 = maybe failure return $ maxSize l1 l2
--   where failure = throwError $ "Cannot assign a universe level to type " ++ printTree e

checkExp :: A.Exp -> VType -> Check Term
checkExp e0 t = do
  case e0 of

    -- Functions

    A.Lam []     e -> checkExp e t
    A.Lam (x:xs) e -> do
      case t of
        VPi dom cl -> addContext (x, dom) $ do
          t' <- applyClosure cl =<< lastVal
          u  <- checkExp (A.Lam xs e) t'
          return $ Lam (_domInfo dom) $ Abs (fromIdU x) u
        _ -> throwError $ "Lambda abstraction expects function type, but got " ++ show t

    e@(A.ELam ez x0 es) -> do
      case t of
        VPi (Dom r (VNat b)) cl -> do
          let x = A.fromIdU x0
          unless (r == Relevant) $ throwError $
            "Extended lambda constructs relevant function: " ++ printTree e
          -- Infer the type of the case expression
          tt <- reifyType t
          -- Make sure that b is a successor size
          -- let failNotSuc = throwError $ "Splitting Nat is only possible at successor size, when checking " ++ printTree e
          -- a  <- maybe failNotSuc return $ sizePred b
          let a = fromMaybe __IMPOSSIBLE__ $ sizePred b
          ta <- reifySize a
          tz <- checkExp ez =<< applyClosure cl (VZero a)
          (ts0, tS0) <-
            addContext (x, Dom Relevant $ VNat a) $ do
              vts <- applyClosure cl =<< do VSuc a <$> lastVal
              tS0 <- reifyType vts
              (,tS0) <$> checkExp es vts
          let ts = Lam Relevant $ Abs x ts0
          let tS = Pi (Dom Relevant $ Nat ta) $ Abs x tS0
          return $ Lam Relevant $ Abs "x" $ App (Var 0) $ raise 1 $
            Case tt tz tS ts

        _ -> throwError $ "Extended lambda is function from Nat _, but here it got type " ++ show t

    e -> do
      (u, ti) <- inferExp e
      coerce u ti t
    -- e -> nyi $ "checking " ++ printTree e

-- | Infers neutrals, natural numbers, types.

inferExp :: A.Exp -> Check (Term, VType)
inferExp e0 = case (e0, appView e0) of

  (e,_) | mustBeType e -> do
    (t, l) <- inferType e
    return (t, VType l)

  (e, (A.Zero, es)) -> do
    case es of
      [ ea ] -> do
        a <- resurrect Irrelevant $ checkSize ea
        (zero a ,) . VNat . vsSuc <$> evaluate a
      _ -> throwError $ "zero expects exactly 1 argument: " ++ printTree e

  (e, (A.Suc, es)) -> do
    case es of
      [ ea, en ] -> do
        a <- resurrect Irrelevant $ checkSize ea
        va <- evaluate a
        n <- checkExp en $ VNat va
        return (suc a n, VNat $ vsSuc va)
      _ -> throwError $ "suc expects exactly 2 arguments: " ++ printTree e

  (e, (A.Fix, es)) -> do
    case es of
      (et : ef : en : []) -> do
        -- Check the motive of elimination
        tT <- checkExp et fixKindV
        -- Check the functional
        let tF = fixType tT
        tf <- checkExp ef =<< evaluate tF
        -- Check the argument
        (tn, a) <- inferNat en
        -- Compute the type of the elimination
        vT <- evaluate tT
        admissible vT
        vn <- evaluate tn
        ve <- applyArgs vT [ Arg ShapeIrr a , Arg Relevant vn ]
        -- Return as postfix application
        return (App tn $ Fix tT tF tf, ve)

      _ -> throwError $ "fix expects exactly 3 arguments: " ++ printTree e

  (A.Infty, _) -> return (Infty, VSize)

  (A.Plus e k, _) -> do
    u <- checkSize e
    return (sPlus u k, VSize)

  -- (A.Plus x k, _) -> do
  --   (u, t) <- inferId x
  --   subType t VSize
  --   return (sPlus u k, t)

  (A.Var A.Under, _) -> throwError "Illegal expression: _"
  (A.Var (A.Id x), _) -> inferId x

  (e0@(A.App f e), _) -> do
    (tf, t) <- inferExp f
    case t of
      VPi (Dom r tdom) cl -> do
        te <- resurrect r $ checkExp e tdom
        v  <- evaluate te
        (App tf $ Apply $ Arg r te,) <$> applyClosure cl v
      _ -> throwError $ "Function type expected in application " ++ printTree e0
             ++ " ; but found type" ++ show t


  (A.Case{}, _)  -> nyi "case"

  (e, _) -> nyi $ "inferring type of " ++ printTree e

-- | Infer type of a variable

inferId :: A.Ident -> Check (Term, VType)
inferId (A.Ident x) = do
  (lookupCxt x <$> asks _envCxt) >>= \case
    Just (i, Dom r t)
      | r == Relevant -> return (Var $ Index i, t)
      | otherwise     -> throwError $ "Illegal reference to " ++ show r ++ " variable: " ++ printTree x

    Nothing     -> do
      (Map.lookup x <$> use stTySigs) >>= \case
        Nothing -> throwError $ "Identifier not in scope: " ++ x
        Just t  -> return (Def x, t)

inferNat :: A.Exp -> Check (Term, VSize)
inferNat e = do
  (u,t) <- inferExp e
  case t of
    VNat a -> return (u, a)
    _ -> throwError $ "Expected natural number, but found " ++ printTree e

-- | Coercion / subtype checking.

coerce :: Term -> VType -> VType -> Check Term
coerce u ti tc = do
  subType ti tc
  return u

-- | Type checker auxiliary functions.

traceCheck :: Print a => a -> b -> b
traceCheck a = trace $ "Checking " ++ printTree a

nyi :: String -> Check a
nyi = throwError . ("Not yet implemented: " ++)

-- | Signature auxiliary functions

lookupTySig :: Id -> Check (Maybe VType)
lookupTySig x = Map.lookup x <$> use stTySigs

lookupDef :: Id -> Check (Maybe Val)
lookupDef x = Map.lookup x <$> use stDefs

addTySig :: Id -> VType -> Check ()
addTySig x t = stTySigs %= Map.insert x t

addDef :: Id -> Val -> Check ()
addDef x v = stDefs %= Map.insert x v

-- * Invoking evaluation

instance MonadEval (Reader (Map Id Val)) where
  getDef x = fromMaybe __IMPOSSIBLE__ . Map.lookup x <$> ask

evaluate :: Term -> Check Val
evaluate t = do
  sig   <- use stDefs
  delta <- asks _envEnv
  return $ runReader (evalIn t delta) sig

applyClosure :: VClos -> Val -> Check Val
applyClosure cl v =
  runReader (applyClos cl v) <$> use stDefs

applyElims :: Val -> VElims -> Check Val
applyElims v es =
  runReader (applyEs v es) <$> use stDefs

applyArgs :: Val -> [Arg Val] -> Check Val
applyArgs v = applyElims v . map Apply

reifyType :: VType -> Check Type
reifyType t = do
  n <- length <$> asks _envCxt
  sig <- use stDefs
  return $ runReader (runReaderT (readbackType t) n) sig

reifySize :: VSize -> Check Size
reifySize t = do
  n <- length <$> asks _envCxt
  sig <- use stDefs
  return $ runReader (runReaderT (readbackSize t) n) sig

-- * Context manipulation

-- | Looking up in the typing context

lookupCxt :: Id -> Cxt -> Maybe (Int, Dom VType)
lookupCxt x cxt = loop 0 cxt
  where
  loop i = \case
    [] -> Nothing
    ((y,t) : cxt)
      | x == y    -> Just (i,t)
      | otherwise -> loop (succ i) cxt


-- | Value of last variable added to context.

lastVal :: Check Val
lastVal = head <$> asks _envEnv

-- | Extending the typing context

class AddContext a where
  addContext :: a -> Check b -> Check b

instance AddContext a => AddContext [a] where
  addContext as = foldr (.) id $ map addContext as

-- A.IdU instances

instance AddContext (A.IdU, Type) where
  addContext (x,t) = addContext (fromIdU x, t)

instance AddContext (A.IdU, VType) where
  addContext (x,t) = addContext (fromIdU x, t)

instance AddContext (A.IdU, Dom VType) where
  addContext (x,t) = addContext (fromIdU x, t)

-- Id instances

instance AddContext (Id, Type) where
  addContext (x,t) cont = do
    t <- evaluate t
    addContext (x,t) cont

instance AddContext (Id, VType) where
  addContext (x,t) = addContext (x, defaultDom t)

instance AddContext (Id, Dom VType) where
  addContext (x,t) = local
    $ over envCxt ((x,t):)
    . over envEnv nextVar
    where nextVar delta = vVar (unDom t) (length delta) : delta

-- | Context: resurrecting irrelevant variables
resurrect :: Relevance -> Check a -> Check a
resurrect = \case
  -- Relevant application: resurrect nothing.
  Relevant   -> id
  -- Irrelevant application: resurrect everything.
  Irrelevant -> local $ over envCxt $ map $ over _2 $ set domInfo Relevant
  -- Shape irrelevant application: resurrect shape-irrelevant variables.
  ShapeIrr   -> local $ over envCxt $ map $ over _2 $ over domInfo $ resSI
    where
    resSI = \case
      ShapeIrr -> Relevant
      r -> r

-- * Subtyping and type equality

subType :: Val -> Val -> Check ()
subType ti tc = do
  let failure = throwError $ "Subtyping failed: type " ++ show ti
        ++ " is not a subtype of " ++ show tc
  case (ti, tc) of
    (VNat  a, VNat  b) -> unless (leqSize a b) failure
    (VType a, VType b) -> unless (leqSize a b) failure
    (VPi dom1 cl1, VPi dom2 cl2) -> do
      unless (_domInfo dom2 <= _domInfo dom1) failure
      subType (unDom dom2) (unDom dom1)
      addContext (absName $ closBody cl2, dom2) $ do
        v  <- lastVal
        b1 <- applyClosure cl1 v
        b2 <- applyClosure cl2 v
        subType b1 b2
    _ -> equalType ti tc

equalType :: Val -> Val -> Check ()
equalType v v' = do
  t  <- reifyType v
  t' <- reifyType v'
  unless (t == t') $
    throwError $ "Inferred type " ++ show t ++ " is not equal to expected type " ++ show t'

-- * Admissibility check for the type of @fix@.

-- | A simple positivity check.
--
--   For the type constructor T of fix we check that
--   @
--     i : ..Size, x : Nat i |- T i x <= T oo x
--   @
--   This does not introduce a new concept an is sound, but excludes
--   @
--     min : forall .i -> Nat i -> Nat i -> Nat i
--   @

admissible :: Val -> Check ()
admissible v = do
  k <- length <$> asks _envCxt
  addContext ("i", VSize) $ do
    va <- lastVal
    addContext ("x", VNat $ va) $ do
      u  <- lastVal
      t1  <- applyArgs v [ Arg ShapeIrr va, Arg Relevant u]
      t2  <- applyArgs v [ Arg ShapeIrr VInfty, Arg Relevant u]
      subType t1 t2

-- | Semi-continuity check (to be completed)

admissibleSemi :: Val -> Check ()
admissibleSemi v = do
  k <- length <$> asks _envCxt
  addContext ("i", VSize) $ do
    va <- lastVal
    addContext ("x", VNat $ va) $ do
      u  <- lastVal
      tv  <- applyArgs v [ Arg ShapeIrr va, Arg Relevant u]
      debug "testing upperSemi" k tv
      ok <- upperSemi k tv
      unless ok $ do
        t <- reifyType tv
        a <- reifySize va
        throwError $
          "Type " ++ show t ++ " of fix needs to be upper semi-continuous in size " ++ show a

debug :: String -> VGen -> VType -> Check ()
debug txt k tv = do
    a <- reifySize $ vsVar k
    t <- reifyType tv
    traceM $ txt ++ " " ++ show a ++ "  " ++ show t

-- | For a function type to be upper semi-continuous,
--   its codomain needs to be so, and
--   the domain needs to be lower semi-continous.

upperSemi :: VGen -> VType -> Check Bool
upperSemi k t = do
  debug "upperSemi" k t
  case t of
    VPi dom cl -> do
      lowerSemi k $ unDom dom
      addContext (absName $ closBody cl, dom) $ do
        v <- lastVal
        upperSemi k =<< applyClosure cl v
    VType{}         -> return True
    VSize           -> return True
    VNat{}          -> return True
    t@(VUp (VType _) _) -> monotone k True t
    t -> do
     traceM $ "upperSemi " ++ show k ++ "  " ++ show t
     __IMPOSSIBLE__

-- | Base types and antitone types are lower semi-continuous.

lowerSemi :: VGen -> VType -> Check Bool
lowerSemi k t = do
  debug "lowerSemi" k t
  case t of
    t@(VPi dom cl)  -> monotone k False t
    VType{}         -> return True
    VSize           -> return True
    VNat{}          -> return True
    t@(VUp (VType _) _) -> monotone k False t
    t -> do
     traceM $ "lowerSemi " ++ show k ++ "  " ++ show t
     __IMPOSSIBLE__

-- antitone :: VGen -> VType -> Check Bool
-- antitone k t = do
--   traceM $ "\nantitone " ++ show k ++ "  " ++ show t
--   return True

monotone :: VGen -> Bool -> VType -> Check Bool
monotone k b t = do
  debug (if b then "monotone" else "antitone") k t
  case t of
    VPi dom cl -> do
      monotone k (not b) $ unDom dom
      addContext (absName $ closBody cl, dom) $ do
        u <- lastVal
        monotone k b =<< applyClosure cl u
    VType a -> monotoneSize k b a
    VNat  a -> monotoneSize k b a
    VSize   -> return True
    -- VInfty  -> return True
    -- VZero _ -> return True
    -- VSuc _ v -> monotone k b v
    -- VLam cl  -> addContext ("#", VSize) $ do
    --   u <- lastVal
    --   monotone k b =<< applyClosure cl u
    -- VUp _ (VNe k' es)
    --   | k == k'   -> return b
    --   | otherwise -> return True
    VUp (VType _) _ -> return True
    _ -> __IMPOSSIBLE__

  -- traceM $ "\nmonotone " ++ show k ++ "  " ++ show t
  -- return True

monotoneSize :: VGen -> Bool -> VSize -> Check Bool
monotoneSize k b t = do
  debugSize (if b then "monotone" else "antitone") k t
  case t of
    VInfty  -> return True
    VZero _ -> return True
    VSuc _ v -> monotoneSize k b v
    VUp _ (VNe k' es)
      | k == k'   -> do
          traceM $ "same var"
          unless b $ throwError "admissibility check failed"
          return b
      | otherwise -> return True
    _ -> __IMPOSSIBLE__

debugSize :: String -> VGen -> VType -> Check ()
debugSize txt k v = do
    a <- reifySize $ vsVar k
    b <- reifySize v
    traceM $ txt ++ " " ++ show a ++ "  " ++ show b