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.Literal as L
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 AST.Variable as Var
21import qualified Elm.Compiler.Imports as Imports
22import qualified Elm.Package as Package
23import Elm.Utils ((|>))
24import qualified Reporting.Annotation as A
25import qualified Reporting.Error.Syntax as Error
26import qualified Reporting.Region as R
27import qualified Reporting.Result as Result
31type Result warning a =
32 Result.Result () warning Error.Error a
39validate :: Module.Source -> Result wrn Module.Valid
40validate (Module.Module name path info) =
42 (ModuleName.Canonical pkgName _) =
45 (Module.Source tag settings docs exports imports decls) =
48 do (ValidStuff ports structure) <- validateDecls decls
50 validEffects <- validateEffects tag settings ports (D._defs structure)
52 return $ Module.Module name path $
53 Module.Valid docs exports (addDefaults pkgName imports) structure validEffects
62 -> [Module.UserImport]
63 -> ([Module.DefaultImport], [Module.UserImport])
64addDefaults pkgName imports =
66 if pkgName == Package.core then
79 -> Module.SourceSettings
80 -> [A.Commented Effects.PortRaw]
81 -> [A.Commented Src.ValidDef]
82 -> Result wrn Effects.Raw
83validateEffects tag settings@(A.A _ pairs) ports validDefs =
86 do noSettings Error.SettingsOnNormalModule settings
91 do noSettings Error.SettingsOnPortModule settings
92 return (Effects.Port ports)
94 Module.Effect tagRegion ->
96 collectSettings (A.A region setting, userValue) dict =
97 Map.insertWith (++) setting [(region, userValue)] dict
100 foldr collectSettings Map.empty pairs
103 managerType <- toManagerType tagRegion settingsDict
104 (r0, r1, r2) <- checkManager tagRegion managerType validDefs
105 return (Effects.Manager () (Effects.Info tagRegion r0 r1 r2 managerType))
108noSettings :: Error.Error -> Module.SourceSettings -> Result wrn ()
109noSettings errorMsg (A.A region settings) =
115 Result.throw region errorMsg
118noPorts :: [A.Commented Effects.PortRaw] -> Result wrn ()
126 toError (A.A (region, _) (Effects.PortRaw name _)) =
127 A.A region (Error.UnexpectedPort name)
129 Result.throwMany (map toError ports)
134 -> Map.Map Text [(R.Region, A.Located Text)]
135 -> Result wrn Effects.ManagerType
136toManagerType tagRegion settingsDict =
138 toErrors name entries =
139 map (\entry -> A.A (fst entry) (Error.BadSettingOnEffectModule name)) entries
143 |> Map.delete "command"
144 |> Map.delete "subscription"
145 |> Map.mapWithKey toErrors
149 do when (not (null errors)) (Result.throwMany errors)
151 (,) <$> extractOne "command" settingsDict
152 <*> extractOne "subscription" settingsDict
154 -- TODO check that cmd and sub types exist?
156 (Nothing, Nothing) ->
157 Result.throw tagRegion Error.NoSettingsOnEffectModule
159 (Just cmd, Nothing) ->
160 return (Effects.CmdManager cmd)
162 (Nothing, Just sub) ->
163 return (Effects.SubManager sub)
165 (Just cmd, Just sub) ->
166 return (Effects.FxManager cmd sub)
171 -> Map.Map Text [(R.Region, A.Located Text)]
172 -> Result wrn (Maybe (A.Located Text))
173extractOne name settingsDict =
174 case Map.lookup name settingsDict of
179 error "Empty lists should never be added to the dictionary of effect module settings."
181 Just [(_, userType)] ->
182 return (Just userType)
184 Just ((region, _) : _) ->
185 Result.throw region (Error.DuplicateSettingOnEffectModule name)
189-- CHECK EFFECT MANAGER
194 -> Effects.ManagerType
195 -> [A.Commented Src.ValidDef]
196 -> Result wrn (R.Region, R.Region, R.Region)
197checkManager tagRegion managerType validDefs =
200 Map.fromList (Maybe.mapMaybe getSimpleDefRegion validDefs)
203 <$> requireMaps tagRegion regionDict managerType
204 <*> requireRegion tagRegion regionDict "init"
205 <*> requireRegion tagRegion regionDict "onEffects"
206 <*> requireRegion tagRegion regionDict "onSelfMsg"
209getSimpleDefRegion :: A.Commented Src.ValidDef -> Maybe (Text, R.Region)
210getSimpleDefRegion decl =
212 A.A _ (Src.Def region (A.A _ (Pattern.Var name)) _ _) ->
221 -> Map.Map Text R.Region
222 -> Effects.ManagerType
224requireMaps tagRegion regionDict managerType =
227 when (Map.notMember name regionDict) $
228 Result.throw tagRegion (Error.MissingManagerOnEffectModule name)
231 Effects.CmdManager _ ->
234 Effects.SubManager _ ->
237 Effects.FxManager _ _ ->
238 check "cmdMap" <* check "subMap"
241requireRegion :: R.Region -> Map.Map Text R.Region -> Text -> Result wrn R.Region
242requireRegion tagRegion regionDict name =
243 case Map.lookup name regionDict of
248 Result.throw tagRegion (Error.MissingManagerOnEffectModule name)
255collapseComments :: [D.CommentOr (A.Located a)] -> Result wrn [A.Commented a]
256collapseComments listWithComments =
257 case listWithComments of
261 D.Comment (A.A _ msg) : D.Whatever (A.A region a) : rest ->
264 A.A (region, Just msg) a
266 fmap (entry:) (collapseComments rest)
268 D.Comment (A.A region _) : rest ->
269 collapseComments rest
270 <* Result.throw region Error.CommentOnNothing
273 D.Whatever (A.A region a) : rest ->
276 A.A (region, Nothing) a
278 fmap (entry:) (collapseComments rest)
282-- VALIDATE STRUCTURED SOURCE
285validateDecls :: [D.Source] -> Result wrn ValidStuff
286validateDecls sourceDecls =
287 do rawDecls <- collapseComments sourceDecls
289 validStuff <- validateRawDecls rawDecls
291 let (D.Decls _ unions aliases _) = _structure validStuff
294 <* F.traverse_ checkTypeVarsInUnion unions
295 <* F.traverse_ checkTypeVarsInAlias aliases
296 <* checkDuplicates validStuff
300-- VALIDATE DECLARATIONS
305 { _ports :: [A.Commented Effects.PortRaw]
306 , _structure :: D.Valid
310validateRawDecls :: [A.Commented D.Raw] -> Result wrn ValidStuff
311validateRawDecls commentedDecls =
312 vrdHelp commentedDecls [] (D.Decls [] [] [] [])
316 :: [A.Commented D.Raw]
317 -> [A.Commented Effects.PortRaw]
319 -> Result wrn ValidStuff
320vrdHelp commentedDecls ports structure =
321 case commentedDecls of
323 Result.ok (ValidStuff ports structure)
325 A.A ann decl : rest ->
327 D.Union (D.Type name tvars ctors) ->
328 vrdHelp rest ports (D.addUnion (A.A ann (D.Type name tvars ctors)) structure)
330 D.Alias (D.Type name tvars alias) ->
331 vrdHelp rest ports (D.addAlias (A.A ann (D.Type name tvars alias)) structure)
334 vrdHelp rest ports (D.addInfix fixity structure)
336 D.Def (A.A region def) ->
337 vrdDefHelp rest (A.A (region, snd ann) def) ports structure
340 do checkDuplicateFieldsInType tipe
341 vrdHelp rest (A.A ann (Effects.PortRaw name tipe) : ports) structure
345 :: [A.Commented D.Raw]
346 -> A.Commented Src.RawDef'
347 -> [A.Commented Effects.PortRaw]
349 -> Result wrn ValidStuff
350vrdDefHelp remainingDecls (A.A ann def) ports structure =
352 addDef validDef (ValidStuff finalPorts struct) =
353 ValidStuff finalPorts (D.addDef (A.A ann validDef) struct)
356 Src.Definition pat expr ->
358 <$> validateDef (fst ann) pat expr Nothing
359 <*> vrdHelp remainingDecls ports structure
361 Src.Annotation name tipe ->
362 case remainingDecls of
363 A.A _ (D.Def (A.A defRegion (Src.Definition pat expr))) : rest
364 | Pattern.isVar name pat ->
366 <$> validateDef (R.merge (fst ann) defRegion) pat expr (Just tipe)
367 <*> vrdHelp rest ports structure
370 vrdHelp remainingDecls ports structure
371 <* Result.throw (fst ann) (Error.TypeWithoutDefinition name)
375-- VALIDATE DEFINITIONS
378definitions :: [Src.RawDef] -> Result wrn [Src.ValidDef]
379definitions sourceDefs =
380 do validDefs <- definitionsHelp sourceDefs
383 |> map Src.getPattern
384 |> concatMap Pattern.boundVars
385 |> detectDuplicates Error.DuplicateDefinition
390definitionsHelp :: [Src.RawDef] -> Result wrn [Src.ValidDef]
391definitionsHelp sourceDefs =
396 A.A defRegion (Src.Definition pat expr) : rest ->
398 <$> validateDef defRegion pat expr Nothing
399 <*> definitionsHelp rest
401 A.A annRegion (Src.Annotation name tipe) : rest ->
403 A.A defRegion (Src.Definition pat expr) : rest'
404 | Pattern.isVar name pat ->
406 <$> validateDef (R.merge annRegion defRegion) pat expr (Just tipe)
407 <*> definitionsHelp rest'
410 Result.throw annRegion (Error.TypeWithoutDefinition name)
418 -> Result wrn Src.ValidDef
419validateDef region pat expr maybeType =
420 do validExpr <- expression expr
421 validateDefPattern pat validExpr
422 F.traverse_ checkDuplicateFieldsInType maybeType
423 return $ Src.Def region pat validExpr maybeType
426validateDefPattern :: Pattern.Raw -> Src.ValidExpr -> Result wrn ()
427validateDefPattern pattern body =
428 case fst (Src.collectLambdas body) of
434 A.A _ (Pattern.Var funcName) ->
435 checkArguments funcName args
439 (A.A start _) = pattern
440 (A.A end _) = last args
442 Result.throw (R.merge start end) (Error.BadFunctionName (length args))
445checkArguments :: Text -> [Pattern.Raw] -> Result wrn ()
446checkArguments funcName args =
449 concatMap Pattern.boundVars args
451 checkDups seenArgs (A.A region arg) =
452 if Set.member arg seenArgs then
453 Result.throw region (Error.DuplicateArgument funcName arg)
456 return (Set.insert arg seenArgs)
458 foldM_ checkDups Set.empty vars
462-- VALIDATE EXPRESSIONS
465expression :: Src.RawExpr -> Result wrn Src.ValidExpr
466expression (A.A ann sourceExpression) =
468 case sourceExpression of
469 Src.Var (Var.Raw "True") ->
470 return (Src.Literal (L.Boolean True))
472 Src.Var (Var.Raw "False") ->
473 return (Src.Literal (L.Boolean False))
478 Src.Lambda pattern body ->
480 <$> validatePattern pattern
483 Src.Binop ops lastExpr ->
485 <$> traverse (\(expr,op) -> (,) <$> expression expr <*> pure op) ops
486 <*> expression lastExpr
488 Src.Case e branches ->
491 <*> traverse (\(p,b) -> (,) <$> validatePattern p <*> expression b) branches
493 Src.Ctor name args ->
494 Src.Ctor name <$> traverse expression args
497 return (Src.Literal lit)
499 Src.List expressions ->
501 <$> traverse expression expressions
503 Src.App funcExpr argExpr ->
505 <$> expression funcExpr
506 <*> expression argExpr
508 Src.If branches finally ->
510 <$> traverse both branches
511 <*> expression finally
513 Src.Access record field ->
515 <$> expression record
518 Src.Update record fields ->
519 do checkDuplicateFields ann fields
520 Src.Update <$> expression record <*> traverse second fields
523 do checkDuplicateFields ann fields
524 Src.Record <$> traverse second fields
531 Src.GLShader uid src gltipe ->
532 return (Src.GLShader uid src gltipe)
535second :: (a, Src.RawExpr) -> Result wrn (a, Src.ValidExpr)
536second (value, expr) =
537 (,) value <$> expression expr
540both :: (Src.RawExpr, Src.RawExpr) -> Result wrn (Src.ValidExpr, Src.ValidExpr)
542 (,) <$> expression expr1 <*> expression expr2
545checkDuplicateFields :: R.Region -> [(A.Located Text, a)] -> Result wrn ()
546checkDuplicateFields recordRegion fields =
548 checkDups seenFields ( A.A region field, _ ) =
549 if Set.member field seenFields then
550 Result.throw recordRegion (Error.DuplicateFieldName region field)
552 Result.ok (Set.insert field seenFields)
554 foldM_ checkDups Set.empty fields
561validatePattern :: Pattern.Raw -> Result wrn Pattern.Raw
562validatePattern pattern =
563 do detectDuplicates Error.BadPattern (Pattern.boundVars pattern)
571checkDuplicates :: ValidStuff -> Result wrn ()
572checkDuplicates (ValidStuff ports (D.Decls defs unions aliases _)) =
577 concatMap (Pattern.boundVars . Src.getPattern . A.drop) defs
582 fromPort (A.A (region, _) (Effects.PortRaw name _)) =
587 (types, typeValues) =
588 unzip (map fromUnion unions ++ map fromAlias aliases)
590 fromUnion (A.A (region, _) (D.Type name _ ctors)) =
592 , map (A.A region . fst) ctors
595 fromAlias (A.A (region, _) (D.Type name _ (A.A _ tipe))) =
596 (,) (A.A region name) $
605 [ detectDuplicates Error.DuplicateValueDeclaration (portValues ++ defValues)
606 , detectDuplicates Error.DuplicateValueDeclaration (concat typeValues)
607 , detectDuplicates Error.DuplicateTypeDeclaration types
611detectDuplicates :: (Text -> Error.Error) -> [A.Located Text] -> Result wrn ()
612detectDuplicates tag names =
614 add (A.A region name) dict =
615 Map.insertWith (++) name [region] dict
618 Map.toList (foldr add Map.empty pairs)
620 check (name, regions) =
623 Result.throw region (tag name)
628 F.traverse_ check (makeGroups names)
632-- UNBOUND TYPE VARIABLES
635checkTypeVarsInUnion :: A.Commented (D.Union Type.Raw) -> Result wrn ()
636checkTypeVarsInUnion (A.A (region,_) (D.Type name boundVars ctors)) =
637 do allFreeVars <- concat <$> traverse freeVars (concatMap snd ctors)
638 case diff boundVars allFreeVars of
644 (Error.UnboundTypeVarsInUnion name boundVars unbound)
647checkTypeVarsInAlias :: A.Commented (D.Alias Type.Raw) -> Result wrn ()
648checkTypeVarsInAlias (A.A (region,_) (D.Type name boundVars tipe)) =
649 do allFreeVars <- freeVars tipe
650 case diff boundVars allFreeVars of
656 (Error.UnboundTypeVarsInAlias name boundVars unbound)
660 (Error.UnusedTypeVarsInAlias name boundVars unused)
664 (Error.MessyTypeVarsInAlias name boundVars unused unbound)
667diff :: [Text] -> [A.Located Text] -> ([Text], [Text])
674 Set.fromList (map A.drop right)
676 ( Set.toList (Set.difference leftSet rightSet)
677 , Set.toList (Set.difference rightSet leftSet)
681freeVars :: Type.Raw -> Result wrn [A.Located Text]
682freeVars (A.A region tipe) =
684 Type.RLambda t1 t2 ->
685 (++) <$> freeVars t1 <*> freeVars t2
688 Result.ok [A.A region x]
694 concat <$> traverse freeVars (t:ts)
696 Type.RRecord fields ext ->
697 do checkDuplicateFields region fields
698 let types = Maybe.maybeToList ext ++ map snd fields
699 concat <$> traverse freeVars types
702checkDuplicateFieldsInType :: Type.Raw -> Result wrn ()
703checkDuplicateFieldsInType (A.A region tipe) =
705 Type.RLambda t1 t2 ->
706 checkDuplicateFieldsInType t1
707 <* checkDuplicateFieldsInType t2
716 F.traverse_ checkDuplicateFieldsInType (t:ts)
718 Type.RRecord fields ext ->
719 do checkDuplicateFields region fields
720 let types = Maybe.maybeToList ext ++ map snd fields
721 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