module Language.Sifflet.TypeCheck
(valueType, typeCheckValues
, typeVarsIn
, Subst
, subst, substComp, idSubst, deltaSubst
, extend, unify
, TypeScheme(..), schematicsTS, unknownsTS, substTS
, TypeEnv, emptyTypeEnv, install, unknownsTE, substTE
, NameSupply(..), newNameSupply, nameSupplyNext, nameSupplyTake
, tcExpr, tcExprs
, tcVar, newInstance
, arrow
, envToTypeEnv, baseTypeEnv
, fromLambdaType, decideTypes
)
where
import Debug.Trace
import Control.Monad (foldM)
import qualified Data.List as AList (lookup)
import Data.Map as Map hiding (filter, foldr, lookup, map, null)
import qualified Data.Map as Map (map, lookup)
import Text.Printf
import Language.Sifflet.Expr
import Text.Sifflet.Repr
import Language.Sifflet.Util
valueType :: Value -> SuccFail Type
valueType v =
case v of
VBool _ -> Succ typeBool
VChar _ -> Succ typeChar
VNumber _ -> Succ typeNum
VString _ -> Succ typeString
VFun (Function _ atypes rtype _) -> Succ $ typeFunction atypes rtype
VList [] -> Succ $ typeList $ TypeVar "list_element"
VList (x:xs) ->
do
xtype <- valueType x
xstypes <- mapM valueType xs
if filter (/= xtype) xstypes == []
then Succ $ typeList xtype
else Fail "list with diverse element types"
typeCheckValues :: [String] -> [Type] -> [Value] -> SuccFail [Value]
typeCheckValues names types values =
let check :: (Map String Type) -> [String] -> [Type] -> [Value]
-> SuccFail [Value]
check _ [] [] [] = Succ []
check env (n:ns) (t:ts) (v:vs) =
case typeMatch t v env of
Succ env' -> check env' ns ts vs >>= Succ . (v:)
Fail msg -> Fail $ "For variable " ++ n ++ ":\n" ++ msg
check _ _ _ _ = error "typeCheckValues: mismatched list lengths"
in check empty names types values
typeMatch :: Type -> Value -> (Map String Type) -> SuccFail (Map String Type)
typeMatch atype value env =
let sorry x etype =
Fail $ repr x ++ ": " ++ etype ++ " expected"
in case (atype, value) of
(TypeCons "Bool" [], VBool _) -> Succ env
(TypeCons "Bool" [], x) -> sorry x "True or False"
(TypeCons "Char" [], VChar _) -> Succ env
(TypeCons "Char" [], x) -> sorry x "character"
(TypeCons "Num" [], VNumber _) -> Succ env
(TypeCons "Num" [], x) -> sorry x "number"
(TypeCons "String" [], VString _) -> Succ env
(TypeCons "String" [], x) -> sorry x "string"
(TypeVar name, avalue) ->
case Map.lookup name env of
Nothing ->
valueType avalue >>= \ vtype ->
Succ $ Map.insert name vtype env
Just concreteType -> typeMatch concreteType avalue env
(TypeCons "List" [etype], VList lvalues) ->
case lvalues of
[] -> Succ env
v:vs ->
typeMatch etype v env >>=
typeMatch (typeList etype) (VList vs)
(TypeCons "Function" [_, _], _) ->
error "typeMatch: unimplemented case for TypeCons Function"
_ -> Fail $ "type mismatch: " ++ show (atype, value)
typeVarsIn :: Type -> [TypeVarName]
typeVarsIn atype =
let tvarsIn atype' result =
case atype' of
TypeVar vname -> vname : result
TypeCons _ctor ts -> foldr tvarsIn result ts
in tvarsIn atype []
type Subst = TypeVarName -> Type
subst :: Subst -> Type -> Type
subst phi typeExpr =
case typeExpr of
TypeVar tvn -> phi tvn
TypeCons ctor atypes ->
TypeCons ctor (map (subst phi) atypes)
substComp :: Subst -> Subst -> Subst
substComp sub2 sub1 tvn = subst sub2 (sub1 tvn)
idSubst :: Subst
idSubst tvn = TypeVar tvn
deltaSubst :: TypeVarName -> Type -> Subst
deltaSubst tvn typeExpr tvn' =
if tvn == tvn'
then typeExpr
else TypeVar tvn'
extend :: Subst -> TypeVarName -> Type -> SuccFail Subst
extend phi tvn t =
if t == TypeVar tvn
then Succ phi
else if tvn `elem` typeVarsIn t
then Fail ("occurs check: " ++ tvn ++ " is in " ++ (show t))
else Succ (deltaSubst tvn t `substComp` phi)
unify :: Subst -> (Type, Type) -> SuccFail Subst
unify phi (TypeVar tvn, t) =
if phi tvn == TypeVar tvn
then extend phi tvn (subst phi t)
else unify phi (phi tvn, subst phi t)
unify phi (TypeCons tcn ts, TypeVar tvn) =
unify phi (TypeVar tvn, TypeCons tcn ts)
unify phi types@(TypeCons tcn ts, TypeCons tcn' ts') =
if tcn == tcn'
then unifyList phi (zip ts ts')
else Fail ("cannot unify " ++ show (fst types) ++
" with " ++ show (snd types))
unifyList :: Subst -> [(Type, Type)] -> SuccFail Subst
unifyList = foldM unify
data TypeScheme = TypeScheme [TypeVarName] Type
deriving (Eq, Show)
schematicsTS :: TypeScheme -> [TypeVarName]
schematicsTS (TypeScheme schVars _) = schVars
unknownsTS :: TypeScheme -> [TypeVarName]
unknownsTS (TypeScheme schVars t) =
[tv | tv <- typeVarsIn t, not (tv `elem` schVars)]
substTS :: Subst -> TypeScheme -> TypeScheme
substTS phi (TypeScheme schVars t) =
let phi' :: Subst
phi' tvn =
if tvn `elem` schVars
then TypeVar tvn
else phi tvn
in TypeScheme schVars (subst phi' t)
type TypeEnv = Map TypeVarName TypeScheme
emptyTypeEnv :: TypeEnv
emptyTypeEnv = Map.empty
rng :: TypeEnv -> [TypeScheme]
rng = elems
install :: TypeEnv -> TypeVarName -> TypeScheme -> TypeEnv
install te tvn ts = insert tvn ts te
unknownsTE :: TypeEnv -> [TypeVarName]
unknownsTE gamma = concatMap unknownsTS (rng gamma)
substTE :: Subst -> TypeEnv -> TypeEnv
substTE phi gamma = Map.map (substTS phi) gamma
data NameSupply = NameSupply TypeVarName Int
deriving (Eq, Show)
newNameSupply :: TypeVarName -> NameSupply
newNameSupply prefix = NameSupply prefix 1
nameSupplyNext :: NameSupply -> (TypeVarName, NameSupply)
nameSupplyNext (NameSupply prefix count) =
(prefix ++ show count, NameSupply prefix (count + 1))
nameSupplyTake :: NameSupply -> Int -> ([TypeVarName], NameSupply)
nameSupplyTake (NameSupply prefix count) n =
([prefix ++ show i | i <- [count .. count+n1]],
NameSupply prefix (count + n))
tcExpr :: TypeEnv -> NameSupply -> Expr -> SuccFail (Subst, Type, NameSupply)
tcExpr te ns expr =
let prim ptype = Succ (idSubst, ptype, ns)
in case expr of
EUndefined ->
let (tvn, ns') = nameSupplyNext ns
in Succ (idSubst, TypeVar tvn, ns')
EBool _ -> prim typeBool
EChar _ -> prim typeChar
ENumber _ -> prim typeNum
EString _ -> prim typeString
ESymbol (Symbol name) -> tcVar te ns name
EIf c a b -> tcIf te ns c a b
EList exprs -> tcList te ns exprs
ELambda (Symbol x) body -> tcLambda te ns x body
EApp f arg -> tcApp te ns f arg
ECall _ _ -> tcExpr te ns (callToApp expr)
EOp _op _ _ -> Fail "tcExpr: not implemented for EOp"
EGroup e -> tcExpr te ns e
tcExprs :: TypeEnv -> NameSupply -> [Expr]
-> SuccFail (Subst, [Type], NameSupply)
tcExprs _te ns [] = Succ (idSubst, [], ns)
tcExprs te ns (e:es) = do
{
(phi, t, ns') <- tcExpr te ns e
; tcExprs1 te ns' es phi t
}
tcExprs1 :: TypeEnv -> NameSupply -> [Expr] -> Subst -> Type
-> SuccFail (Subst, [Type], NameSupply)
tcExprs1 te ns es phi t = do
{
(psi, ts, ns') <- tcExprs (substTE phi te) ns es
; tcExprs2 phi t psi ts ns'
}
tcExprs2 :: Subst -> Type -> Subst -> [Type] -> NameSupply
-> SuccFail (Subst, [Type], NameSupply)
tcExprs2 phi t psi ts ns =
Succ (substComp psi phi, subst psi t : ts, ns)
tcVar :: TypeEnv -> NameSupply -> String -> SuccFail (Subst, Type, NameSupply)
tcVar te ns name =
case Map.lookup name te of
Nothing ->
Fail ("BUG in type checker: tcVar: unable to find " ++ name ++
" in type environment")
Just ntype ->
let (t, ns') = newInstance ns ntype
in Succ (idSubst, t, ns')
newInstance :: NameSupply -> TypeScheme -> (Type, NameSupply)
newInstance ns (TypeScheme schVars t) =
let (names, ns') = nameSupplyTake ns (length schVars)
alist = zip schVars names
phi = alistToSubst alist
in (subst phi t, ns')
alistToSubst :: [(TypeVarName, TypeVarName)] -> Subst
alistToSubst alist tvn =
case AList.lookup tvn alist of
Just tvn' -> TypeVar tvn'
Nothing -> TypeVar tvn
tcApp :: TypeEnv -> NameSupply -> Expr -> Expr
-> SuccFail (Subst, Type, NameSupply)
tcApp te ns e1 e2 =
let (tvn, ns') = nameSupplyNext ns
in do
(phi, [t1, t2], ns'') <- tcExprs te ns' [e1, e2]
phi' <- unify phi (t1, t2 `arrow` (TypeVar tvn))
Succ (phi', phi' tvn, ns'')
arrow :: Type -> Type -> Type
arrow a b = TypeCons "Function" [a, b]
tcLambda :: TypeEnv -> NameSupply -> String -> Expr
-> SuccFail (Subst, Type, NameSupply)
tcLambda te ns x e =
let (tvn, ns') = nameSupplyNext ns
te' = install te x (TypeScheme [] (TypeVar tvn))
in do
{
(phi, t, ns'') <- tcExpr te' ns' e
; Succ (phi, phi tvn `arrow` t, ns'')
}
decideTypes :: String -> Expr -> [String] -> Env -> SuccFail([Type], Type)
decideTypes name body args env =
let te = envToTypeEnv env
te' = install te name (TypeScheme [] (TypeVar "u"))
result :: SuccFail (Type, ([Type], Type))
result = do
expr <- traceValue "decideTypes.expr" $ toLambdaExpr args body
(_, t, _) <- tcExpr te' (newNameSupply "t") expr
_ <- traceValue "decideTypes.t" $ return t
res <- traceValue "decideTypes.res" $ fromLambdaType t
Succ (t, res)
finalResult =
case result of
Succ (t, res@(argTypes, _retType)) ->
if length args == length argTypes
then Succ res
else Fail ("Inferred function type " ++ show t ++
" does not agree with number of arguments")
Fail msg -> Fail msg
in traceValue (printf "decideTypes: %s ::\n" (show (args, body)))
finalResult
traceValue :: (Show a) => String -> a -> a
traceValue label value =
trace (printf "\n%s: %s\n" label (show value)) value
fromLambdaType :: Type -> SuccFail ([Type], Type)
fromLambdaType t =
case t of
TypeCons "Function" [t1, t2] ->
case t2 of
TypeCons "Function" _ -> do
(atypes, rtype) <- fromLambdaType t2
return (t1:atypes, rtype)
_ -> Succ ([t1], t2)
TypeCons "Function" _ ->
error ("fromLambdaType: invalid function type " ++ show t)
_ -> error ("fromLambdaType: non-function type " ++ show t)
tcIf :: TypeEnv -> NameSupply -> Expr -> Expr -> Expr
-> SuccFail (Subst, Type, NameSupply)
tcIf te ns expr1 expr2 expr3 = do
{
(phi, ts, ns') <- tcExprs te ns [expr1, expr2, expr3]
;
phi' <- unifyList phi [(head ts, typeBool), (ts !! 1, ts !! 2)]
; return (phi', subst phi' (ts !! 1), ns')
}
tcList :: TypeEnv -> NameSupply -> [Expr]
-> SuccFail (Subst, Type, NameSupply)
tcList te ns exprs =
case exprs of
[] ->
let (tvn, ns') = nameSupplyNext ns
in Succ (idSubst, typeList (TypeVar tvn), ns')
e:es ->
do
{
(phi, ts, ns') <- tcExprs te ns [e, EList es]
;
phi' <- unify phi (typeList (head ts), ts !! 1)
; return (phi', subst phi' (ts !! 1), ns')
}
envToTypeEnv :: Env -> TypeEnv
envToTypeEnv env =
let frameTE frame = Map.map vftype frame
in Map.unions (Prelude.map frameTE env)
baseTypeEnv :: TypeEnv
baseTypeEnv = Map.map vftype (head baseEnv)
vftype :: Value -> TypeScheme
vftype (VFun f) = ftype f
vftype _ = error "vftype: non-function value type"
ftype :: Function -> TypeScheme
ftype f =
let ft = uncurry typeFunction (functionArgResultTypes f)
scvars = typeVarsIn ft
in TypeScheme scvars ft