module Type.ExtraChecks (mainType, occurs, portTypes) where
import Control.Applicative ((<$>),(<*>))
import Control.Monad.Error
import Control.Monad.State
import qualified Data.Map as Map
import qualified Data.Traversable as Traverse
import qualified Data.UnionFind.IO as UF
import Text.PrettyPrint as P
import qualified AST.Annotation as A
import qualified AST.Expression.Canonical as Canonical
import qualified AST.PrettyPrint as PP
import qualified AST.Type as ST
import qualified AST.Variable as V
import qualified Transform.Expression as Expr
import qualified Type.Type as TT
import qualified Type.State as TS
throw :: [Doc] -> Either [Doc] a
throw err = Left [ P.vcat err ]
mainType :: TS.Env -> ErrorT [P.Doc] IO (Map.Map String ST.CanonicalType)
mainType environment =
do environment' <- liftIO $ Traverse.traverse TT.toSrcType environment
mainCheck environment'
where
mainCheck :: (Monad m) => Map.Map String ST.CanonicalType
-> ErrorT [P.Doc] m (Map.Map String ST.CanonicalType)
mainCheck env =
case Map.lookup "main" env of
Nothing -> return env
Just typeOfMain
| tipe `elem` acceptable -> return env
| otherwise -> throwError err
where
acceptable = [ "Graphics.Element.Element"
, "Signal.Signal Graphics.Element.Element" ]
tipe = PP.renderPretty typeOfMain
err = [ P.text "Type Error: 'main' must have type Element or (Signal Element)."
, P.text "Instead 'main' has type:\n"
, P.nest 4 (PP.pretty typeOfMain)
, P.text " " ]
data Direction = In | Out
portTypes :: (Monad m) => Canonical.Expr -> ErrorT [P.Doc] m ()
portTypes expr =
case Expr.checkPorts (check In) (check Out) expr of
Left err -> throwError err
Right _ -> return ()
where
check = isValid True False False
isValid isTopLevel seenFunc seenSignal direction name tipe =
case tipe of
ST.Aliased _ t -> valid t
ST.Type v
| V.isJson v -> return ()
| V.isPrimitive v -> return ()
| V.isTuple v -> return ()
| otherwise -> err "an unsupported type"
ST.App t [] -> valid t
ST.App (ST.Type v) [t]
| V.isSignal v -> handleSignal t
| V.isMaybe v -> valid t
| V.isArray v -> valid t
| V.isList v -> valid t
ST.App (ST.Type v) ts
| V.isTuple v -> mapM_ valid ts
ST.App _ _ -> err "an unsupported type"
ST.Var _ -> err "free type variables"
ST.Lambda _ _ ->
case direction of
In -> err "functions"
Out | seenFunc -> err "higher-order functions"
| seenSignal -> err "signals that contain functions"
| otherwise ->
forM_ (ST.collectLambdas tipe)
(isValid' True seenSignal direction name)
ST.Record _ (Just _) -> err "extended records with free type variables"
ST.Record fields Nothing ->
mapM_ (\(k,v) -> (,) k <$> valid v) fields
where
isValid' = isValid False
valid = isValid' seenFunc seenSignal direction name
handleSignal t
| seenFunc = err "functions that involve signals"
| seenSignal = err "signals-of-signals"
| isTopLevel = isValid' seenFunc True direction name t
| otherwise = err "a signal within a data stucture"
dir inMsg outMsg = case direction of { In -> inMsg ; Out -> outMsg }
txt = P.text . concat
err kind =
throw $
[ txt [ "Type Error: the value ", dir "coming in" "sent out"
, " through port '", name, "' is invalid." ]
, txt [ "It contains ", kind, ":\n" ]
, P.nest 4 (PP.pretty tipe) <> P.text "\n"
, txt [ "Acceptable values for ", dir "incoming" "outgoing", " ports include:" ]
, txt [ " Ints, Floats, Bools, Strings, Maybes, Lists, Arrays, Tuples, unit values," ]
, txt [ " Json.Values, ", dir "" "first-order functions, ", "and concrete records." ]
]
occurs :: (String, TT.Variable) -> StateT TS.SolverState IO ()
occurs (name, variable) =
do vars <- liftIO $ infiniteVars [] variable
case vars of
[] -> return ()
var:_ -> do
desc <- liftIO $ UF.descriptor var
case TT.structure desc of
Nothing ->
modify $ \s -> s { TS.sErrors = P.text msg : TS.sErrors s }
Just _ ->
do liftIO $ UF.setDescriptor var (desc { TT.structure = Nothing })
var' <- liftIO $ UF.fresh desc
TS.addError (A.None (P.text name)) (Just msg) var var'
where
msg = "Infinite types are not allowed"
infiniteVars :: [TT.Variable] -> TT.Variable -> IO [TT.Variable]
infiniteVars seen var =
let go = infiniteVars (var:seen) in
if var `elem` seen
then return [var]
else do
desc <- UF.descriptor var
case TT.structure desc of
Nothing -> return []
Just struct ->
case struct of
TT.App1 a b -> (++) <$> go a <*> go b
TT.Fun1 a b -> (++) <$> go a <*> go b
TT.Var1 a -> go a
TT.EmptyRecord1 -> return []
TT.Record1 fields ext -> concat <$> mapM go (ext : concat (Map.elems fields))