1{-# OPTIONS_GHC -Wall #-}
2{-# LANGUAGE OverloadedStrings #-}
3module Validate (Result, validate) where
5import Control.Monad (foldM_, when)
6import qualified Data.Foldable as F
7import qualified Data.Map as Map
8import qualified Data.Maybe as Maybe
9import qualified Data.Set as Set
10import Data.Text (Text)
12import qualified AST.Effects as Effects
13import qualified AST.Expression.Source as Src
14import qualified AST.Declaration as D
15import qualified AST.Module as Module
16import qualified AST.Module.Name as ModuleName
17import qualified AST.Pattern as Pattern
18import qualified AST.Type as Type
19import qualified Elm.Compiler.Imports as Imports
20import qualified Elm.Package as Package
21import Elm.Utils ((|>))
22import qualified Reporting.Annotation as A
23import qualified Reporting.Error.Syntax as Error
24import qualified Reporting.Region as R
25import qualified Reporting.Result as Result
29type Result warning a =
30 Result.Result () warning Error.Error a
37validate :: Module.Source -> Result wrn Module.Valid
38validate (Module.Module name path info) =
40 (ModuleName.Canonical pkgName _) =
43 (Module.Source tag settings docs exports imports decls) =
46 do (ValidStuff ports structure) <- validateDecls decls
48 validEffects <- validateEffects tag settings ports (D._defs structure)
50 return $ Module.Module name path $
51 Module.Valid docs exports (addDefaults pkgName imports) structure validEffects
60 -> [Module.UserImport]
61 -> ([Module.DefaultImport], [Module.UserImport])
62addDefaults pkgName imports =
64 if pkgName == Package.core then
77 -> Module.SourceSettings
78 -> [A.Commented Effects.PortRaw]
79 -> [A.Commented Src.ValidDef]
80 -> Result wrn Effects.Raw
81validateEffects tag settings@(A.A _ pairs) ports validDefs =
84 do noSettings Error.SettingsOnNormalModule settings
89 do noSettings Error.SettingsOnPortModule settings
90 return (Effects.Port ports)
92 Module.Effect tagRegion ->
94 collectSettings (A.A region setting, userValue) dict =
95 Map.insertWith (++) setting [(region, userValue)] dict
98 foldr collectSettings Map.empty pairs
101 managerType <- toManagerType tagRegion settingsDict
102 (r0, r1, r2) <- checkManager tagRegion managerType validDefs
103 return (Effects.Manager () (Effects.Info tagRegion r0 r1 r2 managerType))
106noSettings :: Error.Error -> Module.SourceSettings -> Result wrn ()
107noSettings errorMsg (A.A region settings) =
113 Result.throw region errorMsg
116noPorts :: [A.Commented Effects.PortRaw] -> Result wrn ()
124 toError (A.A (region, _) (Effects.PortRaw name _)) =
125 A.A region (Error.UnexpectedPort name)
127 Result.throwMany (map toError ports)
132 -> Map.Map Text [(R.Region, A.Located Text)]
133 -> Result wrn Effects.ManagerType
134toManagerType tagRegion settingsDict =
136 toErrors name entries =
137 map (\entry -> A.A (fst entry) (Error.BadSettingOnEffectModule name)) entries
141 |> Map.delete "command"
142 |> Map.delete "subscription"
143 |> Map.mapWithKey toErrors
147 do when (not (null errors)) (Result.throwMany errors)
149 (,) <$> extractOne "command" settingsDict
150 <*> extractOne "subscription" settingsDict
152 -- TODO check that cmd and sub types exist?
154 (Nothing, Nothing) ->
155 Result.throw tagRegion Error.NoSettingsOnEffectModule
157 (Just cmd, Nothing) ->
158 return (Effects.CmdManager cmd)
160 (Nothing, Just sub) ->
161 return (Effects.SubManager sub)
163 (Just cmd, Just sub) ->
164 return (Effects.FxManager cmd sub)
169 -> Map.Map Text [(R.Region, A.Located Text)]
170 -> Result wrn (Maybe (A.Located Text))
171extractOne name settingsDict =
172 case Map.lookup name settingsDict of
177 error "Empty lists should never be added to the dictionary of effect module settings."
179 Just [(_, userType)] ->
180 return (Just userType)
182 Just ((region, _) : _) ->
183 Result.throw region (Error.DuplicateSettingOnEffectModule name)
187-- CHECK EFFECT MANAGER
192 -> Effects.ManagerType
193 -> [A.Commented Src.ValidDef]
194 -> Result wrn (R.Region, R.Region, R.Region)
195checkManager tagRegion managerType validDefs =
198 Map.fromList (Maybe.mapMaybe getSimpleDefRegion validDefs)
201 <$> requireMaps tagRegion regionDict managerType
202 <*> requireRegion tagRegion regionDict "init"
203 <*> requireRegion tagRegion regionDict "onEffects"
204 <*> requireRegion tagRegion regionDict "onSelfMsg"
207getSimpleDefRegion :: A.Commented Src.ValidDef -> Maybe (Text, R.Region)
208getSimpleDefRegion decl =
210 A.A _ (Src.Def region (A.A _ (Pattern.Var name)) _ _) ->
219 -> Map.Map Text R.Region
220 -> Effects.ManagerType
222requireMaps tagRegion regionDict managerType =
225 when (Map.notMember name regionDict) $
226 Result.throw tagRegion (Error.MissingManagerOnEffectModule name)
229 Effects.CmdManager _ ->
232 Effects.SubManager _ ->
235 Effects.FxManager _ _ ->
236 check "cmdMap" <* check "subMap"
239requireRegion :: R.Region -> Map.Map Text R.Region -> Text -> Result wrn R.Region
240requireRegion tagRegion regionDict name =
241 case Map.lookup name regionDict of
246 Result.throw tagRegion (Error.MissingManagerOnEffectModule name)
253collapseComments :: [D.CommentOr (A.Located a)] -> Result wrn [A.Commented a]
254collapseComments listWithComments =
255 case listWithComments of
259 D.Comment (A.A _ msg) : D.Whatever (A.A region a) : rest ->
262 A.A (region, Just msg) a
264 fmap (entry:) (collapseComments rest)
266 D.Comment (A.A region _) : rest ->
267 collapseComments rest
268 <* Result.throw region Error.CommentOnNothing
271 D.Whatever (A.A region a) : rest ->
274 A.A (region, Nothing) a
276 fmap (entry:) (collapseComments rest)
280-- VALIDATE STRUCTURED SOURCE
283validateDecls :: [D.Source] -> Result wrn ValidStuff
284validateDecls sourceDecls =
285 do rawDecls <- collapseComments sourceDecls
287 validStuff <- validateRawDecls rawDecls
289 let (D.Decls _ unions aliases _) = _structure validStuff
292 <* F.traverse_ checkTypeVarsInUnion unions
293 <* F.traverse_ checkTypeVarsInAlias aliases
294 <* checkDuplicates validStuff
298-- VALIDATE DECLARATIONS
303 { _ports :: [A.Commented Effects.PortRaw]
304 , _structure :: D.Valid
308validateRawDecls :: [A.Commented D.Raw] -> Result wrn ValidStuff
309validateRawDecls commentedDecls =
310 vrdHelp commentedDecls [] (D.Decls [] [] [] [])
314 :: [A.Commented D.Raw]
315 -> [A.Commented Effects.PortRaw]
317 -> Result wrn ValidStuff
318vrdHelp commentedDecls ports structure =
319 case commentedDecls of
321 Result.ok (ValidStuff ports structure)
323 A.A ann decl : rest ->
325 D.Union (D.Type name tvars ctors) ->
326 vrdHelp rest ports (D.addUnion (A.A ann (D.Type name tvars ctors)) structure)
328 D.Alias (D.Type name tvars alias) ->
329 vrdHelp rest ports (D.addAlias (A.A ann (D.Type name tvars alias)) structure)
332 vrdHelp rest ports (D.addInfix fixity structure)
334 D.Def (A.A region def) ->
335 vrdDefHelp rest (A.A (region, snd ann) def) ports structure
338 do checkDuplicateFieldsInType tipe
339 vrdHelp rest (A.A ann (Effects.PortRaw name tipe) : ports) structure
343 :: [A.Commented D.Raw]
344 -> A.Commented Src.RawDef'
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 Src.Definition pat expr ->
356 <$> validateDef (fst ann) pat expr Nothing
357 <*> vrdHelp remainingDecls ports structure
359 Src.Annotation name tipe ->
360 case remainingDecls of
361 A.A _ (D.Def (A.A defRegion (Src.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 :: [Src.RawDef] -> Result wrn [Src.ValidDef]
377definitions sourceDefs =
378 do validDefs <- definitionsHelp sourceDefs
381 |> map Src.getPattern
382 |> concatMap Pattern.boundVars
383 |> detectDuplicates Error.DuplicateDefinition
388definitionsHelp :: [Src.RawDef] -> Result wrn [Src.ValidDef]
389definitionsHelp sourceDefs =
394 A.A defRegion (Src.Definition pat expr) : rest ->
396 <$> validateDef defRegion pat expr Nothing
397 <*> definitionsHelp rest
399 A.A annRegion (Src.Annotation name tipe) : rest ->
401 A.A defRegion (Src.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 Src.ValidDef
417validateDef region pat expr maybeType =
418 do validExpr <- expression expr
419 validateDefPattern pat validExpr
420 F.traverse_ checkDuplicateFieldsInType maybeType
421 return $ Src.Def region pat validExpr maybeType
424validateDefPattern :: Pattern.Raw -> Src.ValidExpr -> Result wrn ()
425validateDefPattern pattern body =
426 case fst (Src.collectLambdas body) of
432 A.A _ (Pattern.Var funcName) ->
433 checkArguments funcName args
437 (A.A start _) = pattern
438 (A.A end _) = last args
440 Result.throw (R.merge start end) (Error.BadFunctionName (length args))
443checkArguments :: Text -> [Pattern.Raw] -> Result wrn ()
444checkArguments funcName args =
447 concatMap Pattern.boundVars args
449 checkDups seenArgs (A.A region arg) =
450 if Set.member arg seenArgs then
451 Result.throw region (Error.DuplicateArgument funcName arg)
454 return (Set.insert arg seenArgs)
456 foldM_ checkDups Set.empty vars
460-- VALIDATE EXPRESSIONS
463expression :: Src.RawExpr -> Result wrn Src.ValidExpr
464expression (A.A ann sourceExpression) =
466 case sourceExpression of
470 Src.Lambda pattern body ->
472 <$> validatePattern pattern
475 Src.Binop ops lastExpr ->
477 <$> traverse (\(expr,op) -> (,) <$> expression expr <*> pure op) ops
478 <*> expression lastExpr
480 Src.Case e branches ->
483 <*> traverse (\(p,b) -> (,) <$> validatePattern p <*> expression b) branches
485 Src.Ctor name args ->
486 Src.Ctor name <$> traverse expression args
489 return (Src.Literal lit)
491 Src.List expressions ->
493 <$> traverse expression expressions
495 Src.App funcExpr argExpr ->
497 <$> expression funcExpr
498 <*> expression argExpr
500 Src.If branches finally ->
502 <$> traverse both branches
503 <*> expression finally
505 Src.Access record field ->
507 <$> expression record
510 Src.Update record fields ->
511 do checkDuplicateFields ann fields
512 Src.Update <$> expression record <*> traverse second fields
515 do checkDuplicateFields ann fields
516 Src.Record <$> traverse second fields
523 Src.GLShader uid src gltipe ->
524 return (Src.GLShader uid src gltipe)
527second :: (a, Src.RawExpr) -> Result wrn (a, Src.ValidExpr)
528second (value, expr) =
529 (,) value <$> expression expr
532both :: (Src.RawExpr, Src.RawExpr) -> Result wrn (Src.ValidExpr, Src.ValidExpr)
534 (,) <$> expression expr1 <*> expression expr2
537checkDuplicateFields :: R.Region -> [(A.Located Text, a)] -> Result wrn ()
538checkDuplicateFields recordRegion fields =
540 checkDups seenFields ( A.A region field, _ ) =
541 if Set.member field seenFields then
542 Result.throw recordRegion (Error.DuplicateFieldName region field)
544 Result.ok (Set.insert field seenFields)
546 foldM_ checkDups Set.empty fields
553validatePattern :: Pattern.Raw -> Result wrn Pattern.Raw
554validatePattern pattern =
555 do detectDuplicates Error.BadPattern (Pattern.boundVars pattern)
563checkDuplicates :: ValidStuff -> Result wrn ()
564checkDuplicates (ValidStuff ports (D.Decls defs unions aliases _)) =
569 concatMap (Pattern.boundVars . Src.getPattern . A.drop) defs
574 fromPort (A.A (region, _) (Effects.PortRaw name _)) =
579 (types, typeValues) =
580 unzip (map fromUnion unions ++ map fromAlias aliases)
582 fromUnion (A.A (region, _) (D.Type name _ ctors)) =
584 , map (A.A region . fst) ctors
587 fromAlias (A.A (region, _) (D.Type name _ (A.A _ tipe))) =
588 (,) (A.A region name) $
597 [ detectDuplicates Error.DuplicateValueDeclaration (portValues ++ defValues)
598 , detectDuplicates Error.DuplicateValueDeclaration (concat typeValues)
599 , detectDuplicates Error.DuplicateTypeDeclaration types
603detectDuplicates :: (Text -> Error.Error) -> [A.Located Text] -> Result wrn ()
604detectDuplicates tag names =
606 add (A.A region name) dict =
607 Map.insertWith (++) name [region] dict
610 Map.toList (foldr add Map.empty pairs)
612 check (name, regions) =
615 Result.throw region (tag name)
620 F.traverse_ check (makeGroups names)
624-- UNBOUND TYPE VARIABLES
627checkTypeVarsInUnion :: A.Commented (D.Union Type.Raw) -> Result wrn ()
628checkTypeVarsInUnion (A.A (region,_) (D.Type name boundVars ctors)) =
629 do allFreeVars <- concat <$> traverse freeVars (concatMap snd ctors)
630 case diff boundVars allFreeVars of
636 (Error.UnboundTypeVarsInUnion name boundVars unbound)
639checkTypeVarsInAlias :: A.Commented (D.Alias Type.Raw) -> Result wrn ()
640checkTypeVarsInAlias (A.A (region,_) (D.Type name boundVars tipe)) =
641 do allFreeVars <- freeVars tipe
642 case diff boundVars allFreeVars of
648 (Error.UnboundTypeVarsInAlias name boundVars unbound)
652 (Error.UnusedTypeVarsInAlias name boundVars unused)
656 (Error.MessyTypeVarsInAlias name boundVars unused unbound)
659diff :: [Text] -> [A.Located Text] -> ([Text], [Text])
666 Set.fromList (map A.drop right)
668 ( Set.toList (Set.difference leftSet rightSet)
669 , Set.toList (Set.difference rightSet leftSet)
673freeVars :: Type.Raw -> Result wrn [A.Located Text]
674freeVars (A.A region tipe) =
676 Type.RLambda t1 t2 ->
677 (++) <$> freeVars t1 <*> freeVars t2
680 Result.ok [A.A region x]
686 concat <$> traverse freeVars (t:ts)
688 Type.RRecord fields ext ->
689 do checkDuplicateFields region fields
690 let types = Maybe.maybeToList ext ++ map snd fields
691 concat <$> traverse freeVars types
694checkDuplicateFieldsInType :: Type.Raw -> Result wrn ()
695checkDuplicateFieldsInType (A.A region tipe) =
697 Type.RLambda t1 t2 ->
698 checkDuplicateFieldsInType t1
699 <* checkDuplicateFieldsInType t2
708 F.traverse_ checkDuplicateFieldsInType (t:ts)
710 Type.RRecord fields ext ->
711 do checkDuplicateFields region fields
712 let types = Maybe.maybeToList ext ++ map snd fields
713 F.traverse_ checkDuplicateFieldsInType types
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