1{-# OPTIONS_GHC -Wall #-}
2module Validate (Result, module') where
4import Control.Monad (foldM_, when)
5import qualified Data.Map as Map
6import qualified Data.Maybe as Maybe
7import qualified Data.Set as Set
8import qualified Data.Foldable as F
9import qualified Data.Traversable as T
11import AST.Expression.General as Expr
12import qualified AST.Effects as Effects
13import qualified AST.Expression.Source as Source
14import qualified AST.Expression.Valid as Valid
15import qualified AST.Declaration as D
16import qualified AST.Module as Module
17import qualified AST.Module.Name as ModuleName
18import qualified AST.Pattern as Pattern
19import qualified AST.Type as Type
20import qualified Elm.Compiler.Imports as Imports
21import qualified Elm.Package as Package
22import Elm.Utils ((|>))
23import qualified Reporting.Annotation as A
24import qualified Reporting.Error.Syntax as Error
25import qualified Reporting.Region as R
26import qualified Reporting.Result as Result
30type Result warning a =
31 Result.Result () warning Error.Error a
38module' :: Module.Source -> Result wrn Module.Valid
39module' (Module.Module name path info) =
41 (ModuleName.Canonical pkgName _) =
44 (Module.Source tag settings docs exports imports decls) =
47 do (ValidStuff ports structure) <- validateDecls decls
49 validEffects <- validateEffects tag settings ports (D._defs structure)
51 return $ Module.Module name path $
52 Module.Valid docs exports (addDefaults pkgName imports) structure validEffects
61 -> [Module.UserImport]
62 -> ([Module.DefaultImport], [Module.UserImport])
63addDefaults pkgName imports =
65 if pkgName == Package.core then
78 -> Module.SourceSettings
79 -> [A.Commented Effects.PortRaw]
80 -> [A.Commented Valid.Def]
81 -> Result wrn Effects.Raw
82validateEffects tag settings@(A.A _ pairs) ports validDefs =
85 do noSettings Error.SettingsOnNormalModule settings
90 do noSettings Error.SettingsOnPortModule settings
91 return (Effects.Port ports)
93 Module.Effect tagRegion ->
95 collectSettings (A.A region setting, userValue) dict =
96 Map.insertWith (++) setting [(region, userValue)] dict
99 foldr collectSettings Map.empty pairs
102 managerType <- toManagerType tagRegion settingsDict
103 (r0, r1, r2) <- checkManager tagRegion managerType validDefs
104 return (Effects.Manager () (Effects.Info tagRegion r0 r1 r2 managerType))
107noSettings :: Error.Error -> Module.SourceSettings -> Result wrn ()
108noSettings errorMsg (A.A region settings) =
114 Result.throw region errorMsg
117noPorts :: [A.Commented Effects.PortRaw] -> Result wrn ()
125 toError (A.A (region, _) (Effects.PortRaw name _)) =
126 A.A region (Error.UnexpectedPort name)
128 Result.throwMany (map toError ports)
133 -> Map.Map String [(R.Region, A.Located String)]
134 -> Result wrn Effects.ManagerType
135toManagerType tagRegion settingsDict =
137 toErrors name entries =
138 map (\entry -> A.A (fst entry) (Error.BadSettingOnEffectModule name)) entries
142 |> Map.delete "command"
143 |> Map.delete "subscription"
144 |> Map.mapWithKey toErrors
148 do when (not (null errors)) (Result.throwMany errors)
150 (,) <$> extractOne "command" settingsDict
151 <*> extractOne "subscription" settingsDict
153 -- TODO check that cmd and sub types exist?
155 (Nothing, Nothing) ->
156 Result.throw tagRegion Error.NoSettingsOnEffectModule
158 (Just cmd, Nothing) ->
159 return (Effects.CmdManager cmd)
161 (Nothing, Just sub) ->
162 return (Effects.SubManager sub)
164 (Just cmd, Just sub) ->
165 return (Effects.FxManager cmd sub)
170 -> Map.Map String [(R.Region, A.Located String)]
171 -> Result wrn (Maybe (A.Located String))
172extractOne name settingsDict =
173 case Map.lookup name settingsDict of
178 error "Empty lists should never be added to the dictionary of effect module settings."
180 Just [(_, userType)] ->
181 return (Just userType)
183 Just ((region, _) : _) ->
184 Result.throw region (Error.DuplicateSettingOnEffectModule name)
188-- CHECK EFFECT MANAGER
193 -> Effects.ManagerType
194 -> [A.Commented Valid.Def]
195 -> Result wrn (R.Region, R.Region, R.Region)
196checkManager tagRegion managerType validDefs =
199 Map.fromList (Maybe.mapMaybe getSimpleDefRegion validDefs)
202 <$> requireMaps tagRegion regionDict managerType
203 <*> requireRegion tagRegion regionDict "init"
204 <*> requireRegion tagRegion regionDict "onEffects"
205 <*> requireRegion tagRegion regionDict "onSelfMsg"
208getSimpleDefRegion :: A.Commented Valid.Def -> Maybe (String, R.Region)
209getSimpleDefRegion decl =
211 A.A _ (Valid.Def region (A.A _ (Pattern.Var name)) _ _) ->
220 -> Map.Map String R.Region
221 -> Effects.ManagerType
223requireMaps tagRegion regionDict managerType =
226 when (Map.notMember name regionDict) $
227 Result.throw tagRegion (Error.MissingManagerOnEffectModule name)
230 Effects.CmdManager _ ->
233 Effects.SubManager _ ->
236 Effects.FxManager _ _ ->
237 check "cmdMap" <* check "subMap"
240requireRegion :: R.Region -> Map.Map String R.Region -> String -> Result wrn R.Region
241requireRegion tagRegion regionDict name =
242 case Map.lookup name regionDict of
247 Result.throw tagRegion (Error.MissingManagerOnEffectModule name)
254collapseComments :: [D.CommentOr (A.Located a)] -> Result wrn [A.Commented a]
255collapseComments listWithComments =
256 case listWithComments of
260 D.Comment (A.A _ msg) : D.Whatever (A.A region a) : rest ->
263 A.A (region, Just msg) a
265 fmap (entry:) (collapseComments rest)
267 D.Comment (A.A region _) : rest ->
268 collapseComments rest
269 <* Result.throw region Error.CommentOnNothing
272 D.Whatever (A.A region a) : rest ->
275 A.A (region, Nothing) a
277 fmap (entry:) (collapseComments rest)
281-- VALIDATE STRUCTURED SOURCE
284validateDecls :: [D.Source] -> Result wrn ValidStuff
285validateDecls sourceDecls =
286 do rawDecls <- collapseComments sourceDecls
288 validStuff <- validateRawDecls rawDecls
290 let (D.Decls _ unions aliases _) = _structure validStuff
293 <* F.traverse_ checkTypeVarsInUnion unions
294 <* F.traverse_ checkTypeVarsInAlias aliases
295 <* checkDuplicates validStuff
299-- VALIDATE DECLARATIONS
304 { _ports :: [A.Commented Effects.PortRaw]
305 , _structure :: D.Valid
309validateRawDecls :: [A.Commented D.Raw] -> Result wrn ValidStuff
310validateRawDecls commentedDecls =
311 vrdHelp commentedDecls [] (D.Decls [] [] [] [])
315 :: [A.Commented D.Raw]
316 -> [A.Commented Effects.PortRaw]
318 -> Result wrn ValidStuff
319vrdHelp commentedDecls ports structure =
320 case commentedDecls of
322 Result.ok (ValidStuff ports structure)
324 A.A ann decl : rest ->
326 D.Union (D.Type name tvars ctors) ->
327 vrdHelp rest ports (D.addUnion (A.A ann (D.Type name tvars ctors)) structure)
329 D.Alias (D.Type name tvars alias) ->
330 vrdHelp rest ports (D.addAlias (A.A ann (D.Type name tvars alias)) structure)
333 vrdHelp rest ports (D.addInfix fixity structure)
335 D.Def (A.A region def) ->
336 vrdDefHelp rest (A.A (region, snd ann) def) ports structure
339 vrdHelp rest (A.A ann (Effects.PortRaw name tipe) : ports) structure
343 :: [A.Commented D.Raw]
344 -> A.Commented Source.Def'
345 -> [A.Commented Effects.PortRaw]
347 -> Result wrn ValidStuff
348vrdDefHelp remainingDecls (A.A ann def) ports structure =
350 addDef validDef (ValidStuff finalPorts struct) =
351 ValidStuff finalPorts (D.addDef (A.A ann validDef) struct)
354 Source.Definition pat expr ->
356 <$> validateDef (fst ann) pat expr Nothing
357 <*> vrdHelp remainingDecls ports structure
359 Source.Annotation name tipe ->
360 case remainingDecls of
361 A.A _ (D.Def (A.A defRegion (Source.Definition pat expr))) : rest
362 | Pattern.isVar name pat ->
364 <$> validateDef (R.merge (fst ann) defRegion) pat expr (Just tipe)
365 <*> vrdHelp rest ports structure
368 vrdHelp remainingDecls ports structure
369 <* Result.throw (fst ann) (Error.TypeWithoutDefinition name)
373-- VALIDATE DEFINITIONS
376definitions :: [Source.Def] -> Result wrn [Valid.Def]
377definitions sourceDefs =
378 do validDefs <- definitionsHelp sourceDefs
381 |> map Valid.getPattern
382 |> concatMap Pattern.boundVars
383 |> detectDuplicates Error.DuplicateDefinition
388definitionsHelp :: [Source.Def] -> Result wrn [Valid.Def]
389definitionsHelp sourceDefs =
394 A.A defRegion (Source.Definition pat expr) : rest ->
396 <$> validateDef defRegion pat expr Nothing
397 <*> definitionsHelp rest
399 A.A annRegion (Source.Annotation name tipe) : rest ->
401 A.A defRegion (Source.Definition pat expr) : rest'
402 | Pattern.isVar name pat ->
404 <$> validateDef (R.merge annRegion defRegion) pat expr (Just tipe)
405 <*> definitionsHelp rest'
408 Result.throw annRegion (Error.TypeWithoutDefinition name)
416 -> Result wrn Valid.Def
417validateDef region pat expr maybeType =
418 do validExpr <- expression expr
419 validateDefPattern pat validExpr
420 return $ Valid.Def region pat validExpr maybeType
423validateDefPattern :: Pattern.Raw -> Valid.Expr -> Result wrn ()
424validateDefPattern pattern body =
425 case fst (Expr.collectLambdas body) of
431 A.A _ (Pattern.Var funcName) ->
432 checkArguments funcName args
436 (A.A start _) = pattern
437 (A.A end _) = last args
439 Result.throw (R.merge start end) (Error.BadFunctionName (length args))
442checkArguments :: String -> [Pattern.Raw] -> Result wrn ()
443checkArguments funcName args =
446 concatMap Pattern.boundVars args
448 checkDups seenArgs (A.A region arg) =
449 if Set.member arg seenArgs then
450 Result.throw region (Error.DuplicateArgument funcName arg)
453 return (Set.insert arg seenArgs)
455 foldM_ checkDups Set.empty vars
459-- VALIDATE EXPRESSIONS
462expression :: Source.Expr -> Result wrn Valid.Expr
463expression (A.A ann sourceExpression) =
465 case sourceExpression of
469 Lambda pattern body ->
471 <$> validatePattern pattern
474 Binop op leftExpr rightExpr ->
476 <$> expression leftExpr
477 <*> expression rightExpr
482 <*> T.traverse (\(p,b) -> (,) <$> validatePattern p <*> expression b) branches
485 Data name <$> T.traverse expression args
490 ExplicitList expressions ->
492 <$> T.traverse expression expressions
494 App funcExpr argExpr ->
496 <$> expression funcExpr
497 <*> expression argExpr
499 If branches finally ->
501 <$> T.traverse both branches
502 <*> expression finally
504 Access record field ->
506 <$> expression record
509 Update record fields ->
511 <$> expression record
512 <*> T.traverse second fields
516 checkDups seenFields (field,_) =
517 if Set.member field seenFields then
518 Result.throw ann (Error.DuplicateFieldName field)
521 return (Set.insert field seenFields)
523 do foldM_ checkDups Set.empty fields
524 Record <$> T.traverse second fields
532 return (Cmd moduleName)
535 return (Sub moduleName)
537 OutgoingPort name tipe ->
538 return (OutgoingPort name tipe)
540 IncomingPort name tipe ->
541 return (IncomingPort name tipe)
544 error "DANGER - Program AST nodes should not be in validation phase."
546 SaveEnv moduleName effects ->
547 return (SaveEnv moduleName effects)
549 GLShader uid src gltipe ->
550 return (GLShader uid src gltipe)
553second :: (a, Source.Expr) -> Result wrn (a, Valid.Expr)
554second (value, expr) =
555 (,) value <$> expression expr
558both :: (Source.Expr, Source.Expr) -> Result wrn (Valid.Expr, Valid.Expr)
560 (,) <$> expression expr1 <*> expression expr2
567validatePattern :: Pattern.Raw -> Result wrn Pattern.Raw
568validatePattern pattern =
569 do detectDuplicates Error.BadPattern (Pattern.boundVars pattern)
577checkDuplicates :: ValidStuff -> Result wrn ()
578checkDuplicates (ValidStuff ports (D.Decls defs unions aliases _)) =
583 concatMap (Pattern.boundVars . Valid.getPattern . A.drop) defs
588 fromPort (A.A (region, _) (Effects.PortRaw name _)) =
593 (types, typeValues) =
594 unzip (map fromUnion unions ++ map fromAlias aliases)
596 fromUnion (A.A (region, _) (D.Type name _ ctors)) =
598 , map (A.A region . fst) ctors
601 fromAlias (A.A (region, _) (D.Type name _ (A.A _ tipe))) =
602 (,) (A.A region name) $
611 [ detectDuplicates Error.DuplicateValueDeclaration (portValues ++ defValues)
612 , detectDuplicates Error.DuplicateValueDeclaration (concat typeValues)
613 , detectDuplicates Error.DuplicateTypeDeclaration types
617detectDuplicates :: (String -> Error.Error) -> [A.Located String] -> Result wrn ()
618detectDuplicates tag names =
620 add (A.A region name) dict =
621 Map.insertWith (++) name [region] dict
624 Map.toList (foldr add Map.empty pairs)
626 check (name, regions) =
629 Result.throw region (tag name)
634 F.traverse_ check (makeGroups names)
638-- UNBOUND TYPE VARIABLES
641checkTypeVarsInUnion :: A.Commented (D.Union Type.Raw) -> Result wrn ()
642checkTypeVarsInUnion (A.A (region,_) (D.Type name boundVars ctors)) =
643 case diff boundVars (concatMap freeVars (concatMap snd ctors)) of
649 (Error.UnboundTypeVarsInUnion name boundVars unbound)
652checkTypeVarsInAlias :: A.Commented (D.Alias Type.Raw) -> Result wrn ()
653checkTypeVarsInAlias (A.A (region,_) (D.Type name boundVars tipe)) =
654 case diff boundVars (freeVars tipe) of
660 (Error.UnboundTypeVarsInAlias name boundVars unbound)
664 (Error.UnusedTypeVarsInAlias name boundVars unused)
668 (Error.MessyTypeVarsInAlias name boundVars unused unbound)
671diff :: [String] -> [A.Located String] -> ([String], [String])
678 Set.fromList (map A.drop right)
680 ( Set.toList (Set.difference leftSet rightSet)
681 , Set.toList (Set.difference rightSet leftSet)
685freeVars :: Type.Raw -> [A.Located String]
686freeVars (A.A region tipe) =
688 Type.RLambda t1 t2 ->
689 freeVars t1 ++ freeVars t2
698 concatMap freeVars (t:ts)
700 Type.RRecord fields ext ->
701 maybe [] freeVars ext
702 ++ concatMap (freeVars . snd) fields
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list