on master help sign in
Add checks for port types to ensure that only JS values go through ports and that type annotations are correct and consistent
a97b995Evan Czaplicki
compiler/Type/ExtraChecks.hs+96−23
@@ -1,11 +1,12 @@
1{-# OPTIONS_GHC -W #-}
1{-# OPTIONS_GHC -W #-}
2−module Type.ExtraChecks (extraChecks, occursCheck) where
2+module Type.ExtraChecks (mainType, occurs, portTypes) where
3-- This module contains checks to be run *after* type inference has
3-- This module contains checks to be run *after* type inference has
4-- completed successfully. At that point we still need to do occurs
4-- completed successfully. At that point we still need to do occurs
5-- checks and ensure that `main` has an acceptable type.
5-- checks and ensure that `main` has an acceptable type.
6
6
7import Control.Applicative ((<$>),(<*>))
7import Control.Applicative ((<$>),(<*>))
8import Control.Monad.State
8import Control.Monad.State
9+import qualified Data.List as List
9import qualified Data.Map as Map
10import qualified Data.Map as Map
10import qualified Data.UnionFind.IO as UF
11import qualified Data.UnionFind.IO as UF
11import Type.Type ( Variable, structure, Term1(..), toSrcType )
12import Type.Type ( Variable, structure, Term1(..), toSrcType )
@@ -13,31 +14,103 @@ import qualified Type.State as TS
13import qualified Type.Alias as Alias
14import qualified Type.Alias as Alias
14import Text.PrettyPrint as P
15import Text.PrettyPrint as P
15import SourceSyntax.PrettyPrint (pretty)
16import SourceSyntax.PrettyPrint (pretty)
16−import SourceSyntax.Type (Type)
17+import qualified SourceSyntax.Type as T
18+import qualified SourceSyntax.Expression as E
17−import qualified SourceSyntax.Location as Location
19+import qualified SourceSyntax.Location as L
20+import qualified Transform.Expression as Expr
18import qualified Data.Traversable as Traverse
21import qualified Data.Traversable as Traverse
22+import System.IO.Unsafe
19
23
20−extraChecks :: Alias.Rules -> TS.Env -> IO (Either [P.Doc] (Map.Map String Type))
24+throw err = Left [ P.vcat err ]
21−extraChecks rules env =
22− mainCheck rules <$> Traverse.traverse toSrcType env
23
25
26+mainType :: Alias.Rules -> TS.Env -> IO (Either [P.Doc] (Map.Map String T.Type))
27+mainType rules env = mainCheck rules <$> Traverse.traverse toSrcType env
28+ where
24−mainCheck :: Alias.Rules -> Map.Map String Type -> Either [P.Doc] (Map.Map String Type)
29+ mainCheck :: Alias.Rules -> Map.Map String T.Type -> Either [P.Doc] (Map.Map String T.Type)
25−mainCheck rules env =
30+ mainCheck rules env =
26− case Map.lookup "main" env of
31+ case Map.lookup "main" env of
27− Nothing -> Right env
32+ Nothing -> Right env
28− Just mainType
33+ Just mainType
29− | tipe `elem` acceptable -> Right env
34+ | tipe `elem` acceptable -> Right env
30− | otherwise -> Left [ P.vcat err ]
35+ | otherwise -> throw err
31− where
36+ where
37+ acceptable = [ "Graphics.Element.Element"
32− acceptable = ["Graphics.Element.Element","Signal.Signal Graphics.Element.Element"]
38+ , "Signal.Signal Graphics.Element.Element" ]
39+
33− tipe = P.render . pretty $ Alias.canonicalRealias (fst rules) mainType
40+ tipe = P.render . pretty $ Alias.canonicalRealias (fst rules) mainType
34− err = [ P.text "Type Error: 'main' must have type Element or (Signal Element)."
41+ err = [ P.text "Type Error: 'main' must have type Element or (Signal Element)."
35− , P.text "Instead 'main' has type:\n"
42+ , P.text "Instead 'main' has type:\n"
36− , P.nest 4 . pretty $ Alias.realias rules mainType
43+ , P.nest 4 . pretty $ Alias.realias rules mainType
37− , P.text " " ]
44+ , P.text " " ]
45+
46+portTypes :: Alias.Rules -> E.LExpr -> Either [P.Doc] ()
47+portTypes rules expr =
48+ const () <$> Expr.checkPorts checkIn checkOut expr
49+ where
50+ showType tipe = (P.nest 4 . pretty $ Alias.realias rules tipe) <> P.text "\n"
51+
52+ checkIn name st tt =
53+ do t <- getSignal name st
54+ case unsafePerformIO (toSrcType tt) of
55+ T.Lambda a b ->
56+ do isJsType "coming in" name a
57+ if b `elem` okayTypes then return () else throw msg
58+ where
59+ okayTypes = [ t
60+ , T.Data "Maybe.Maybe" [t]
61+ , T.Data "Either.Either" [T.Data "String.String" [], t]
62+ ]
63+ msg = [ P.text $ "Type Error: the handler for port '" ++ name ++
64+ "' returns values of type:\n"
65+ , showType b
66+ , P.text $ "but the port's type annotation requires that the handler returns values"
67+ , P.text $ "with one of the following types:\n"
68+ ] ++ map showType okayTypes ++
69+ [ P.text "If the handler returns a Maybe or Either, invalid values will cause an"
70+ , P.text "error to be thrown to the JS error handler associated with this port." ]
71+
72+ tipe ->
73+ throw [ P.text $ "Type Error: the handler for port '" ++ name ++
74+ "' must be a function!"
75+ , P.text "Instead it was given a value of type:\n"
76+ , showType tipe
77+ ]
78+
79+ checkOut name st =
80+ do t <- getSignal name st
81+ isJsType "sent out" name t
82+
83+ getSignal name tipe =
84+ case tipe of
85+ T.Data "Signal.Signal" [t] -> return t
86+ _ -> throw [ P.text $ "Type Error: port '" ++ name ++ "' must be a Signal,"
87+ , P.text $ "but the type annotation says it has non-Signal type:\n"
88+ , showType tipe
89+ , P.text $ "Maybe instead you want the following type?\n"
90+ , showType (T.Data "Signal.Signal" [tipe])
91+ ]
92+
93+ isJsType msg name tipe =
94+ case tipe of
95+ T.Data ctor ts | okay ctor -> mapM_ (isJsType msg name) ts
96+ | otherwise -> throw $ err msg "Elm values"
97+ T.Var _ -> throw $ err msg "type variables"
98+ T.Lambda _ _ -> throw $ err msg "Elm functions"
99+ T.EmptyRecord -> throw $ err msg "Elm records"
100+ T.Record _ _ -> throw $ err msg "Elm records"
101+ where
102+ okay ctor = and [ List.isPrefixOf "JavaScript." ctor
103+ , 1 == length (filter (=='.') ctor) ]
104+
105+ err msg kind =
106+ [ P.text $ "Type Error: the values " ++ msg ++ " through port '" ++ name ++
107+ "' must be JavaScript values."
108+ , P.text $ "The values sent through this port contain " ++ kind ++ " with type:\n"
109+ , showType tipe
110+ ]
38
111
39−occursCheck :: (String, Variable) -> StateT TS.SolverState IO ()
112+occurs :: (String, Variable) -> StateT TS.SolverState IO ()
40−occursCheck (name, variable) =
113+occurs (name, variable) =
41 do vars <- liftIO $ infiniteVars [] variable
114 do vars <- liftIO $ infiniteVars [] variable
42 case vars of
115 case vars of
43 [] -> return ()
116 [] -> return ()
@@ -49,7 +122,7 @@ occursCheck (name, variable) =
49 Just _ ->
122 Just _ ->
50 do liftIO $ UF.setDescriptor var (desc { structure = Nothing })
123 do liftIO $ UF.setDescriptor var (desc { structure = Nothing })
51 var' <- liftIO $ UF.fresh desc
124 var' <- liftIO $ UF.fresh desc
52− TS.addError (Location.NoSpan name) (Just msg) var var'
125+ TS.addError (L.NoSpan name) (Just msg) var var'
53 where
126 where
54 msg = "Infinite types are not allowed"
127 msg = "Infinite types are not allowed"
55 fallback _ = return $ P.text msg
128 fallback _ = return $ P.text msg
compiler/Type/Inference.hs+4−2
@@ -13,7 +13,7 @@ import SourceSyntax.Location (noneNoDocs)
13import SourceSyntax.Type (Type)
13import SourceSyntax.Type (Type)
14import Text.PrettyPrint
14import Text.PrettyPrint
15import qualified Type.State as TS
15import qualified Type.State as TS
16−import Type.ExtraChecks (extraChecks)
16+import qualified Type.ExtraChecks as Check
17import Control.Monad.State (execStateT, forM)
17import Control.Monad.State (execStateT, forM)
18import Control.Monad.Error (runErrorT, liftIO)
18import Control.Monad.Error (runErrorT, liftIO)
19import qualified Type.Alias as Alias
19import qualified Type.Alias as Alias
@@ -52,4 +52,6 @@ infer interfaces modul = unsafePerformIO $ do
52 let rules = Alias.rules interfaces (aliases modul) (imports modul)
52 let rules = Alias.rules interfaces (aliases modul) (imports modul)
53 case TS.sErrors state of
53 case TS.sErrors state of
54 errors@(_:_) -> Left `fmap` sequence (map ($ rules) (reverse errors))
54 errors@(_:_) -> Left `fmap` sequence (map ($ rules) (reverse errors))
55+ [] -> case Check.portTypes rules (program modul) of
55− [] -> extraChecks rules (Map.difference (TS.sSavedEnv state) header)
56+ Right () -> Check.mainType rules (Map.difference (TS.sSavedEnv state) header)
57+ Left err -> return (Left err)
compiler/Type/Solve.hs+2−2
@@ -9,7 +9,7 @@ import qualified Data.Traversable as Traversable
9import qualified Data.List as List
9import qualified Data.List as List
10import Type.Type
10import Type.Type
11import Type.Unify
11import Type.Unify
12−import qualified Type.ExtraChecks as EC
12+import qualified Type.ExtraChecks as Check
13import qualified Type.State as TS
13import qualified Type.State as TS
14import SourceSyntax.Location (Located(L), SrcSpan)
14import SourceSyntax.Location (Located(L), SrcSpan)
15
15
@@ -120,7 +120,7 @@ solve (L span constraint) =
120 headers <- Map.unions `fmap` mapM (solveScheme span) schemes
120 headers <- Map.unions `fmap` mapM (solveScheme span) schemes
121 TS.modifyEnv $ \env -> Map.union headers env
121 TS.modifyEnv $ \env -> Map.union headers env
122 solve constraint'
122 solve constraint'
123− mapM EC.occursCheck $ Map.toList headers
123+ mapM Check.occurs $ Map.toList headers
124 TS.modifyEnv (\_ -> oldEnv)
124 TS.modifyEnv (\_ -> oldEnv)
125
125
126 CInstance name term -> do
126 CInstance name term -> do