typecheckExpr :: M.Map String Type -> DomainU -> Type -> A.Expr -> TypeM TExpr
typecheckExpr argMap dom resType = 
    let 
      types = domStructs dom
      tc :: A.Expr -> TypeM TExpr
      tc (A.Access e attr) = 
        let eM         = tc e
            structStr  = getStructNameM =<< (texprType <$> eM)
            attrTypM   = lookupAttrType types attr =<< structStr
        in Access <$> eM <*> pure attr <*> attrTypM
      tc (A.Var n) = Var n <$> lookupName n argMap
      tc (A.LitInt i) = pure $ LitInt i
      tc (A.LitBool b) = pure $ LitBool b
      tc A.ResultVar  = pure $ ResultVar resType
      tc (A.Call name args) = 
        case findFunction name dom of
          Just f -> let argsM = mapM (typecheckExpr argMap dom resType) args 
                    in Call name <$> argsM
                                 <*> checkFunctionArgs argMap dom resType f args
          Nothing -> throwError $ "Coudln't find function " ++ name
      
      -- both equality and inequality are dealt with specially
      -- as they are needed to type `null' values.
      tc (A.BinOpExpr (A.RelOp Eq) A.LitNull A.LitNull) = 
        pure $ BinOpExpr (RelOp Eq VoidType) (LitNull VoidType) 
                                             (LitNull VoidType) BoolType
      tc (A.BinOpExpr (A.RelOp Eq) A.LitNull e) =
        let eM     = tc e
            nullM  = LitNull <$> (texprType <$> eM)
        in  BinOpExpr <$> (RelOp Eq <$> (texprType <$> eM))
                      <*> nullM <*> eM <*> pure BoolType
      tc (A.BinOpExpr (A.RelOp Eq) e A.LitNull) =
        let eM     = tc e
            nullM  = LitNull <$> (texprType <$> eM)
        in  BinOpExpr <$> (RelOp Eq <$> (texprType <$> eM)) 
                      <*> eM <*> nullM <*> pure BoolType
            
      tc (A.BinOpExpr (A.RelOp A.Neq) A.LitNull A.LitNull) = 
        pure $ BinOpExpr (RelOp Neq VoidType) (LitNull VoidType) 
                                              (LitNull VoidType) BoolType
      tc (A.BinOpExpr bOp@(A.RelOp Neq) A.LitNull e) =
        let eM     = tc e
            nullM  = LitNull <$> (texprType <$> eM)
        in  BinOpExpr <$> (RelOp Neq <$> (texprType <$> eM))
                      <*> nullM <*> eM <*> pure BoolType
      tc (A.BinOpExpr bOp@(A.RelOp Neq) e A.LitNull) =
        let eM     = tc e
            nullM  = LitNull <$> (texprType <$> eM)
        in  BinOpExpr <$> (RelOp Neq <$> (texprType <$> eM)) 
                      <*> eM <*> nullM <*> pure BoolType
      tc (A.BinOpExpr bOp e1 e2) =
        let e1M  = tc e1
            e2M  = tc e2
            tM   = join $ binOpTypes bOp <$> e1M <*> e2M
        in BinOpExpr (typedBinOp bOp) <$> e1M <*> e2M <*> tM
      tc (A.UnOpExpr uop e) = 
        let eM  = tc e
            tM  = join $ unOpTypes uop <$> eM
        in UnOpExpr uop <$> eM <*> tM
      tc (A.ForAll ds e) = 
        ForAll ds <$> typecheckExpr ds' dom BoolType e
        where
          ds' = declsToMap ds `M.union` argMap
      tc e = throwError $ "Can't typecheck " ++ show e
    in tc
typecheckExpr :: M.Map String Type -> DomainU -> Type -> A.Expr -> TypeM TExpr
typecheckExpr argMap dom resType = 
    let 
      types = domStructs dom
      tc :: A.Expr -> TypeM TExpr
      tc (A.Access e attr) = 
        let eM         = tc e
            structStr  = getStructNameM =<< (texprType <$> eM)
            attrTypM   = lookupAttrType types attr =<< structStr
        in Access <$> eM <*> pure attr <*> attrTypM
      tc (A.Var n) = Var n <$> lookupName n argMap
      tc (A.LitInt i) = pure $ LitInt i
      tc (A.LitBool b) = pure $ LitBool b
      tc A.ResultVar  = pure $ ResultVar resType
      tc (A.Call name args) = 
        case findFunction name dom of
          Just f -> let argsM = mapM (typecheckExpr argMap dom resType) args 
                    in Call name <$> argsM
                                 <*> checkFunctionArgs argMap dom resType f args
          Nothing -> throwError $ "Coudln't find function " ++ name
      
      -- both equality and inequality are dealt with specially
      -- as they are needed to type `null' values.
      tc (A.BinOpExpr (A.RelOp Eq) A.LitNull A.LitNull) = 
        pure $ BinOpExpr (RelOp Eq VoidType) (LitNull VoidType) 
                                             (LitNull VoidType) BoolType
      tc (A.BinOpExpr (A.RelOp Eq) A.LitNull e) =
        let eM     = tc e
            nullM  = LitNull <$> (texprType <$> eM)
        in  BinOpExpr <$> (RelOp Eq <$> (texprType <$> eM))
                      <*> nullM <*> eM <*> pure BoolType
      tc (A.BinOpExpr (A.RelOp Eq) e A.LitNull) =
        let eM     = tc e
            nullM  = LitNull <$> (texprType <$> eM)
        in  BinOpExpr <$> (RelOp Eq <$> (texprType <$> eM)) 
                      <*> eM <*> nullM <*> pure BoolType
            
      tc (A.BinOpExpr (A.RelOp A.Neq) A.LitNull A.LitNull) = 
        pure $ BinOpExpr (RelOp Neq VoidType) (LitNull VoidType) 
                                              (LitNull VoidType) BoolType
      tc (A.BinOpExpr bOp@(A.RelOp Neq) A.LitNull e) =
        let eM     = tc e
            nullM  = LitNull <$> (texprType <$> eM)
        in  BinOpExpr <$> (RelOp Neq <$> (texprType <$> eM))
                      <*> nullM <*> eM <*> pure BoolType
      tc (A.BinOpExpr bOp@(A.RelOp Neq) e A.LitNull) =
        let eM     = tc e
            nullM  = LitNull <$> (texprType <$> eM)
        in  BinOpExpr <$> (RelOp Neq <$> (texprType <$> eM)) 
                      <*> eM <*> nullM <*> pure BoolType
      tc (A.BinOpExpr bOp e1 e2) =
        let e1M  = tc e1
            e2M  = tc e2
            tM   = join $ binOpTypes bOp <$> e1M <*> e2M
        in BinOpExpr (typedBinOp bOp) <$> e1M <*> e2M <*> tM
      tc (A.UnOpExpr uop e) = 
        let eM  = tc e
            tM  = join $ unOpTypes uop <$> eM
        in UnOpExpr uop <$> eM <*> tM
      tc (A.ForAll ds e) = 
        ForAll ds <$> typecheckExpr ds' dom BoolType e
        where
          ds' = declsToMap ds `M.union` argMap
      tc e = throwError $ "Can't typecheck " ++ show e
    in tc
typecheckExpr argMap dom resType = 
    let 
      types = domStructs dom
      tc :: A.Expr -> TypeM TExpr
      tc (A.Access e attr) = 
        let eM         = tc e
            structStr  = getStructNameM =<< (texprType <$> eM)
            attrTypM   = lookupAttrType types attr =<< structStr
        in Access <$> eM <*> pure attr <*> attrTypM
      tc (A.Var n) = Var n <$> lookupName n argMap
      tc (A.LitInt i) = pure $ LitInt i
      tc (A.LitBool b) = pure $ LitBool b
      tc A.ResultVar  = pure $ ResultVar resType
      tc (A.Call name args) = 
        case findFunction name dom of
          Just f -> let argsM = mapM (typecheckExpr argMap dom resType) args 
                    in Call name <$> argsM
                                 <*> checkFunctionArgs argMap dom resType f args
          Nothing -> throwError $ "Coudln't find function " ++ name
      
      -- both equality and inequality are dealt with specially
      -- as they are needed to type `null' values.
      tc (A.BinOpExpr (A.RelOp Eq) A.LitNull A.LitNull) = 
        pure $ BinOpExpr (RelOp Eq VoidType) (LitNull VoidType) 
                                             (LitNull VoidType) BoolType
      tc (A.BinOpExpr (A.RelOp Eq) A.LitNull e) =
        let eM     = tc e
            nullM  = LitNull <$> (texprType <$> eM)
        in  BinOpExpr <$> (RelOp Eq <$> (texprType <$> eM))
                      <*> nullM <*> eM <*> pure BoolType
      tc (A.BinOpExpr (A.RelOp Eq) e A.LitNull) =
        let eM     = tc e
            nullM  = LitNull <$> (texprType <$> eM)
        in  BinOpExpr <$> (RelOp Eq <$> (texprType <$> eM)) 
                      <*> eM <*> nullM <*> pure BoolType
            
      tc (A.BinOpExpr (A.RelOp A.Neq) A.LitNull A.LitNull) = 
        pure $ BinOpExpr (RelOp Neq VoidType) (LitNull VoidType) 
                                              (LitNull VoidType) BoolType
      tc (A.BinOpExpr bOp@(A.RelOp Neq) A.LitNull e) =
        let eM     = tc e
            nullM  = LitNull <$> (texprType <$> eM)
        in  BinOpExpr <$> (RelOp Neq <$> (texprType <$> eM))
                      <*> nullM <*> eM <*> pure BoolType
      tc (A.BinOpExpr bOp@(A.RelOp Neq) e A.LitNull) =
        let eM     = tc e
            nullM  = LitNull <$> (texprType <$> eM)
        in  BinOpExpr <$> (RelOp Neq <$> (texprType <$> eM)) 
                      <*> eM <*> nullM <*> pure BoolType
      tc (A.BinOpExpr bOp e1 e2) =
        let e1M  = tc e1
            e2M  = tc e2
            tM   = join $ binOpTypes bOp <$> e1M <*> e2M
        in BinOpExpr (typedBinOp bOp) <$> e1M <*> e2M <*> tM
      tc (A.UnOpExpr uop e) = 
        let eM  = tc e
            tM  = join $ unOpTypes uop <$> eM
        in UnOpExpr uop <$> eM <*> tM
      tc (A.ForAll ds e) = 
        ForAll ds <$> typecheckExpr ds' dom BoolType e
        where
          ds' = declsToMap ds `M.union` argMap
      tc e = throwError $ "Can't typecheck " ++ show e
    in tc