packages feed

jl-0.1.0: src/JL/Inferer.hs

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE FlexibleContexts #-}

-- |

module JL.Inferer where


import           Control.Monad.State.Strict
import qualified Data.HashMap.Strict as HM
import           Data.Map (Map)
import qualified Data.Map as M
import           Data.Set (Set)
import qualified Data.Set as S
import           Data.Text (Text)
import qualified Data.Text as T
import           JL.Printer
import           JL.Types

-- | Get the type of the expression.
infer :: Map Variable Type -> Expression -> [TypeVariable] -> Type
infer ctx t stream =
  case evalState (check ctx t) stream of
    (ty, cs) ->
      let s = evalState (unify (S.toList cs)) ()
      in replace s ty

-- | Check the exp with the given context.
check
  :: MonadState ([TypeVariable]) m
  => Map Variable Type -> Expression -> m (Type, Set (Type, Type))
check ctx expr =
  case expr of
    VariableExpression name@(Variable text) ->
      case M.lookup name ctx of
        Nothing -> error ("Not in scope: `" <> T.unpack text <> "'")
        Just typ -> return (typ, mempty)
    LambdaExpression x body -> do
      sym <- generateTypeVariable
      let xty = VariableType sym
      (rty, cs) <- check (M.insert x xty ctx) body
      return (FunctionType xty rty, cs)
    ConstantExpression {} -> return (JSONType, mempty)
    ApplicationExpression f x -> do
      (fty, cs1) <- check ctx f
      (xty, cs2) <- check ctx x
      sym <- generateTypeVariable
      let rty = VariableType sym
          cs = S.insert (fty, FunctionType xty rty) (cs1 <> cs2)
      return (rty, cs)
    InfixExpression l f r -> do
      (fty, cs1) <- check ctx (VariableExpression f)
      (ty1, cs2) <- check ctx l
      (ty2, cs3) <- check ctx r
      sym <- generateTypeVariable
      let rty = VariableType sym
          cs =
            S.insert
              (fty, FunctionType ty1 (FunctionType ty2 rty))
              (cs1 <> cs2 <> cs3)
      return (rty, cs)
    IfExpression cond a b -> do
      (condty, cs1) <- check ctx cond
      (aty, cs2) <- check ctx a
      (bty, cs3) <- check ctx b
      sym <- generateTypeVariable
      let rty = VariableType sym
          cs =
            S.insert
              (condty, JSONType)
              (S.insert (aty, bty) (cs1 <> cs2 <> cs3))
      pure (rty, cs)
    RecordExpression pairs -> do
      cs <-
        foldM
          (\cs (_, e) -> do
             (pty, cs') <- check ctx e
             pure (S.insert (pty, JSONType) (cs <> cs')))
          mempty
          (HM.toList pairs)
      pure (JSONType, cs)
    SubscriptExpression e ks -> do
      (t1, c1) <-
        (case e of
           ExpressionSubscripted es -> check ctx es
           WildcardSubscripted -> pure (FunctionType JSONType JSONType, mempty))
      cs <-
        foldM
          (\cs s ->
             case s of
               PropertySubscript {} -> pure cs
               ExpressionSubscript es -> do
                 (pty, cs') <- check ctx es
                 pure (S.insert (pty, JSONType) (cs <> cs')))
          c1
          ks
      let rty = case e of
                  WildcardSubscripted -> FunctionType JSONType JSONType
                  _ -> JSONType
      pure
        ( rty
        , S.insert (t1, rty) cs)
    ArrayExpression as -> do
      cs <-
        foldM
          (\cs e -> do
             (pty, cs') <- check ctx e
             pure (S.insert (pty, JSONType) (cs <> cs')))
          mempty
          as
      pure (JSONType, cs)

-- | Generate a fresh type variable.
generateTypeVariable
  :: MonadState ([TypeVariable]) m
  => m TypeVariable
generateTypeVariable =
  get >>= \case
    v:vs -> do
      put vs
      pure v
    _ ->
      error "Ran out of type variables"

-- | Unify the list of constraints.
unify
  :: Monad m
  => [(Type, Type)] -> m (Map TypeVariable Type)
unify [] = return mempty
unify ((a, b):cs)
  | a == b = unify cs
  | VariableType v <- a = unifyVariable v cs a b
  | VariableType v <- b = unifyVariable v cs b a
  | FunctionType a1 b1 <- a
  , FunctionType a2 b2 <- b = unify ([(a1, a2), (b1, b2)] <> cs)
  | otherwise =
    error
      (T.unpack
         ("Type " <> quote (prettyType a) <> " doesn't match " <>
          quote (prettyType b)))

-- | Unify a type variable for two types.
unifyVariable
  :: Monad m
  => TypeVariable -> [(Type, Type)] -> Type -> Type -> m (Map TypeVariable Type)
unifyVariable v cs a b =
  if occurs v b
    then error
           (T.unpack ("Occurs check: " <> prettyType a <> " ~ " <> prettyType b))
    else let subbed = M.singleton v b
         in do rest <- unify (substitute subbed cs)
               return (rest <> subbed)

-- | Occurs check.
occurs :: TypeVariable -> Type -> Bool
occurs x (VariableType y)
  | x == y = True
  | otherwise = False
occurs x (FunctionType a b) = occurs x a || occurs x b
occurs _ JSONType = False

-- | Substitute the unified type into the constraints.
substitute :: Map TypeVariable Type -> [(Type, Type)] -> [(Type, Type)]
substitute subs = map go
  where
    go (a, b) = (replace subs a, replace subs b)

-- | Apply a substitution to a type.
replace :: Map TypeVariable Type -> Type -> Type
replace s' t' = M.foldrWithKey go t' s'
  where
    go s1 t (VariableType s2)
      | s1 == s2 = t
      | otherwise = VariableType s2
    go s t (FunctionType t2 t3) = FunctionType (go s t t2) (go s t t3)
    go _ _ JSONType = JSONType

-- | Quote something for showing to the programmer.
quote :: Text -> Text
quote t = "‘" <> t <> "’"