@@ -1,6 +1,13 @@
1{-# OPTIONS_GHC -Wall #-}
1{-# OPTIONS_GHC -Wall #-}
2{-# LANGUAGE OverloadedStrings #-}
2{-# LANGUAGE OverloadedStrings #-}
3−module Nitpick.PatternMatches (patternMatches) where
3+module Nitpick.PatternMatches
5{- The algorithm used here comes from "Warnings for Pattern Matching"
12{- The algorithm used here comes from "Warnings for Pattern Matching"
6by Luc Maranget. Check it out for more information!
13by Luc Maranget. Check it out for more information!
@@ -9,331 +16,551 @@ http://moscova.inria.fr/~maranget/papers/warn/warn.pdf
12−import Control.Arrow ((***), second)
13−import qualified Data.Foldable as F
14import qualified Data.List as List
19import qualified Data.List as List
15import qualified Data.Map as Map
20import qualified Data.Map as Map
16import qualified Data.Maybe as Maybe
21import qualified Data.Maybe as Maybe
17−import qualified Data.Set as Set
18import qualified Data.Text as Text
22import qualified Data.Text as Text
19−import Data.Text (Text)
23+
21−import qualified AST.Expression.Canonical as C
24+import qualified AST.Canonical as Can
22−import qualified AST.Helpers as Help
23−import qualified AST.Literal as L
24−import qualified AST.Module as Module
25−import qualified AST.Module.Name as ModuleName
25+import qualified Elm.Name as N
26−import qualified AST.Pattern as Pattern
27−import qualified AST.Variable as Var
28−import Elm.Utils ((|>))
29−import Nitpick.Pattern (Pattern(..), fromCanonicalPattern)
30−import qualified Optimize.DecisionTree as DT
31import qualified Reporting.Annotation as A
26import qualified Reporting.Annotation as A
32−import qualified Reporting.Error.Pattern as Error
27+import qualified Reporting.Region as R
33−import qualified Reporting.Region as Region
34−import qualified Reporting.Result as Result
38−-- NITPICK PATTERN MATCHES
31+-- PATTERN
41−type Result warning a =
34+data Pattern
42− Result.Result () warning Error.Error a
35+ = Anything
37+ | Ctor Can.CtorAlts N.Name [Pattern]
45−patternMatches :: Module.Interfaces -> Module.Canonical -> Result wrn DT.VariantDict
41+data Literal
46−patternMatches interfaces (Module.Module name info) =
42+ = Chr Text.Text
47− let
43+ | Str Text.Text
48− arityDict =
44+ | Int Int
49− toArityDict interfaces name (Module.unions info)
45+ deriving (Eq)
52− Map.map (Map.map _size) arityDict
55− <* checkExpression arityDict (Module.program info)
58−checkExpression :: ArityDict -> C.Expr -> Result wrn ()
49+-- CREATE SIMPLIFIED PATTERNS
59−checkExpression arityDict (A.A region expression) =
62− checkExpression arityDict
65− go a <* go b
52+simplify :: Can.Pattern -> Pattern
66− in
53+simplify (A.At _ pattern) =
65+ Ctor unitAlts unitName []
67+ Can.PTuple a b Nothing ->
68+ Ctor pairAlts pairName [ simplify a, simplify b ]
70+ Can.PTuple a b (Just c) ->
71+ Ctor tripleAlts tripleName [ simplify a, simplify b, simplify c ]
73+ Can.PCtor _ _ _ alts name args ->
75+ map (\(Can.PatternCtorArg _ _ arg) -> simplify arg) args
78+ foldr cons nil entries
81+ cons hd (simplify tl)
83+ Can.PAlias subPattern _ ->
96+cons :: Can.Pattern -> Pattern -> Pattern
98+ Ctor listAlts consName [ simplify hd, tl ]
104+ Ctor listAlts nilName []
111+{-# NOINLINE unitAlts #-}
112+unitAlts :: Can.CtorAlts
114+ Can.toAlts [ Can.CtorAlt unitName 0 ]
117+{-# NOINLINE pairAlts #-}
118+pairAlts :: Can.CtorAlts
120+ Can.toAlts [ Can.CtorAlt pairName 2 ]
123+{-# NOINLINE tripleAlts #-}
124+tripleAlts :: Can.CtorAlts
126+ Can.toAlts [ Can.CtorAlt tripleName 3 ]
129+{-# NOINLINE listAlts #-}
130+listAlts :: Can.CtorAlts
132+ Can.toAlts [ Can.CtorAlt consName 2, Can.CtorAlt nilName 0 ]
135+{-# NOINLINE unitName #-}
140+{-# NOINLINE pairName #-}
145+{-# NOINLINE tripleName #-}
150+{-# NOINLINE consName #-}
155+{-# NOINLINE nilName #-}
165+ = Incomplete R.Region Context [Pattern]
166+ | Redundant R.Region R.Region Int
179+check :: Can.Module -> Either [Error] ()
180+check (Can.Module _ _ _ decls _ _ _ _) =
181+ case checkDecls decls [] of
193+checkDecls :: Can.Decls -> [Error] -> [Error]
194+checkDecls decls errors =
196+ Can.Declare def subDecls ->
197+ checkDef def $ checkDecls subDecls errors
199+ Can.DeclareRec defs subDecls ->
200+ foldr checkDef (checkDecls subDecls errors) defs
202+ Can.SaveTheEnvironment ->
210+checkDef :: Can.Def -> [Error] -> [Error]
211+checkDef def errors =
213+ Can.Def _ args _ body ->
214+ foldr checkArg (checkExpr body errors) args
216+ Can.TypedDef _ _ args _ body _ ->
217+ foldr checkTypedArg (checkExpr body errors) args
220+checkArg :: Can.Arg -> [Error] -> [Error]
221+checkArg (Can.Arg _ pattern@(A.At region _)) errors =
222+ checkPatterns region BadArg [pattern] errors
225+checkTypedArg :: Can.TypedArg -> [Error] -> [Error]
226+checkTypedArg (Can.TypedArg _ _ pattern@(A.At region _)) errors =
227+ checkPatterns region BadArg [pattern] errors
234+checkExpr :: Can.Expr -> [Error] -> [Error]
235+checkExpr (A.At region expression) errors =
67 case expression of
236 case expression of
68− C.Literal _ ->
237+ Can.VarLocal _ ->
69− Result.ok ()
238+ errors
240+ Can.VarTopLevel _ _ ->
243+ Can.VarKernel _ _ ->
246+ Can.VarForeign _ _ _ ->
249+ Can.VarOperator _ _ _ _ ->
71− C.Var _ ->
264+ Can.List entries ->
72− Result.ok ()
265+ foldr checkExpr errors entries
74− C.List listExprs ->
267+ Can.Negate expr ->
75− F.traverse_ go listExprs
268+ checkExpr expr errors
77− C.Binop _ leftExpr rightExpr ->
270+ Can.Binop _ _ _ _ left right ->
78− go2 leftExpr rightExpr
271+ checkExpr left $
272+ checkExpr right errors
80− C.Lambda pattern@(A.A patRegion _) body ->
274+ Can.Lambda args _ body ->
81− checkPatterns arityDict patRegion Error.Arg [pattern]
275+ foldr checkArg (checkExpr body errors) args
84− C.App func arg ->
277+ Can.Call func args ->
85− go2 func arg
278+ checkExpr func $ foldr checkExpr errors args
87− C.If branches finally ->
280+ Can.If branches finally ->
88− F.traverse_ (uncurry go2) branches
281+ foldr checkIfBranch (checkExpr finally errors) branches
91− C.Let defs body ->
283+ Can.Let def body ->
92− go body
284+ checkDef def $ checkExpr body errors
93− <* F.traverse_ goDef defs
95− goDef (C.Def _ pattern@(A.A patRegion _) expr _) =
96− checkPatterns arityDict patRegion Error.LetBound [pattern]
99− C.Case expr branches ->
286+ Can.LetRec defs body ->
100− go expr
287+ foldr checkDef (checkExpr body errors) defs
101− <* checkPatterns arityDict region Error.Case (map fst branches)
102− <* F.traverse_ (go . snd) branches
104− C.Ctor _ctor exprs ->
289+ Can.LetDestruct pattern@(A.At reg _) _ expr body ->
105− F.traverse_ go exprs
290+ checkPatterns reg BadDestruct [pattern] $
291+ checkExpr expr $ checkExpr body errors
107− C.Access record _field ->
293+ Can.Case expr branches ->
108− go record
294+ checkExpr expr $ checkCases region branches errors
110− C.Update record fields ->
296+ Can.Accessor _ ->
111− go record
297+ errors
112− <* F.traverse_ (go . snd) fields
114− C.Record fields ->
299+ Can.Access record _ ->
115− F.traverse_ (go . snd) fields
300+ checkExpr record errors
117− C.Cmd _ _ ->
302+ Can.Update record fields ->
118− Result.ok ()
303+ checkExpr record $ Map.foldr checkExpr errors fields
120− C.Sub _ _ ->
305+ Can.Record fields ->
121− Result.ok ()
306+ Map.foldr checkExpr errors fields
123− C.OutgoingPort _ _ ->
308+ Can.Unit ->
124− Result.ok ()
309+ errors
126− C.IncomingPort _ _ ->
311+ Can.Tuple a b maybeC ->
127− Result.ok ()
312+ checkExpr a $
329+checkIfBranch :: (Can.Expr, Can.Expr) -> [Error] -> [Error]
330+checkIfBranch (condition, branch) errs =
331+ checkExpr condition $ checkExpr branch errs
335+-- CHECK CASE EXPRESSION
130− error "DANGER - Program AST nodes should not be in Nitpick.PatternMatches."
132− C.SaveEnv _ _ ->
338+checkCases :: R.Region -> [Can.CaseBranch] -> [Error] -> [Error]
133− Result.ok ()
339+checkCases region branches errors =
341+ (patterns, newErrors) =
342+ foldr checkCaseBranch ([], errors) branches
344+ checkPatterns region BadCase patterns newErrors
135− C.GLShader _ _ _ ->
347+checkCaseBranch :: Can.CaseBranch -> ([Can.Pattern], [Error]) -> ([Can.Pattern], [Error])
136− Result.ok ()
348+checkCaseBranch (Can.CaseBranch pattern _ expr) (patterns, errors) =
350+ , checkExpr expr errors
140-- CHECK PATTERNS
355-- CHECK PATTERNS
143−checkPatterns :: ArityDict -> Region.Region -> Error.Origin -> [Pattern.Canonical] -> Result wrn ()
358+checkPatterns :: R.Region -> Context -> [Can.Pattern] -> [Error] -> [Error]
144−checkPatterns arityDict region origin patterns =
359+checkPatterns region context patterns errors =
145− do matrix <- checkRedundant arityDict region [] patterns
360+ case toNonRedundantRows region patterns of
146− case isExhaustive arityDict matrix 1 of
365+ case isExhaustive matrix 1 of
148− Result.ok ()
367+ errors
150 badPatterns ->
369 badPatterns ->
151− Result.throw region (Error.Incomplete origin (map head badPatterns))
370+ Incomplete region context (map head badPatterns) : errors
155-- EXHAUSTIVE PATTERNS
374-- EXHAUSTIVE PATTERNS
379+-- The initial rows "matrix" are all of length 1
380+-- The initial count of items per row "n" is also 1
381+-- The resulting rows are examples of missing patterns
158−isExhaustive :: ArityDict -> [[Pattern]] -> Int -> [[Pattern]]
383+isExhaustive :: [[Pattern]] -> Int -> [[Pattern]]
159−isExhaustive arityDict matrix n =
384+isExhaustive matrix n =
160− case (matrix, n) of
385+ case matrix of
161− ([], _) ->
386+ [] ->
162 [replicate n Anything]
387 [replicate n Anything]
169− ctorSet =
394+ ctors = collectCtors matrix
170− List.foldl' isCompleteHelp Set.empty matrix
395+ numSeen = Map.size ctors
175− if actual == 0 then
397+ if numSeen == 0 then
176− (:) Anything
398+ (:) Anything
177− <$> isExhaustive arityDict (Maybe.mapMaybe toDefault matrix) (n - 1)
399+ <$> isExhaustive (Maybe.mapMaybe specializeRowByAnything matrix) (n - 1)
402+ let alts@(Can.CtorAlts numAlts altList) = snd (Map.findMin ctors) in
403+ if numSeen < numAlts then
405+ <$> Maybe.mapMaybe (isMissing alts ctors) altList
406+ <*> isExhaustive (Maybe.mapMaybe specializeRowByAnything matrix) (n - 1)
181− (ArityInfo expected info) =
410+ isAltExhaustive (Can.CtorAlt name arity) =
182− getArityInfo (Set.findMin ctorSet) arityDict
411+ recoverCtor alts name arity <$>
413+ (Maybe.mapMaybe (specializeRowByCtor name arity) matrix)
184− if actual < expected then
416+ concatMap isAltExhaustive altList
186− <$> Maybe.mapMaybe (isMissing ctorSet) info
187− <*> isExhaustive arityDict (Maybe.mapMaybe toDefault matrix) (n - 1)
191− isExhaustiveHelp (name, arity) =
192− recoverCtor name arity <$>
195− (Maybe.mapMaybe (specialize name arity) matrix)
198− concatMap isExhaustiveHelp info
201−isMissing :: Set.Set Var.Canonical -> (Var.Canonical, Int) -> Maybe Pattern
202−isMissing ctorSet (name, arity) =
203− if Set.member name ctorSet then
419+isMissing :: Can.CtorAlts -> Map.Map N.Name a -> Can.CtorAlt -> Maybe Pattern
420+isMissing alts ctors (Can.CtorAlt name arity) =
421+ if Map.member name ctors then
207− Just (Ctor name (replicate arity Anything))
424+ Just (Ctor alts name (replicate arity Anything))
210−recoverCtor :: Var.Canonical -> Int -> [Pattern] -> [Pattern]
427+recoverCtor :: Can.CtorAlts -> N.Name -> Int -> [Pattern] -> [Pattern]
211−recoverCtor name arity patterns =
428+recoverCtor alts name arity patterns =
213 (args, rest) =
430 (args, rest) =
214 splitAt arity patterns
431 splitAt arity patterns
216− Ctor name args : rest
433+ Ctor alts name args : rest
220-- REDUNDANT PATTERNS
437-- REDUNDANT PATTERNS
223−checkRedundant
440+-- INVARIANT: Produces a list of rows where (forall row. length row == 1)
224− :: ArityDict
441+toNonRedundantRows :: R.Region -> [Can.Pattern] -> Either Error [[Pattern]]
225− -> Region.Region
442+toNonRedundantRows region patterns =
226− -> [[Pattern]]
443+ toSimplifiedUsefulRows region [] patterns
227− -> [Pattern.Canonical]
228− -> Result wrn [[Pattern]]
229−checkRedundant arityDict region checked unchecked =
234− nextPattern@(A.A patRegion _) : rest ->
237− [fromCanonicalPattern nextPattern]
239− if isUseful arityDict checked next then
240− checkRedundant arityDict region (next : checked) rest
242− Result.throw region (Error.Redundant patRegion (length checked + 1))
245−isUseful :: ArityDict -> [[Pattern]] -> [Pattern] -> Bool
446+-- INVARIANT: Produces a list of rows where (forall row. length row == 1)
246−isUseful arityDict matrix vector =
447+toSimplifiedUsefulRows :: R.Region -> [[Pattern]] -> [Can.Pattern] -> Either Error [[Pattern]]
247− case (matrix, vector) of
448+toSimplifiedUsefulRows overallRegion checkedRows uncheckedPatterns =
449+ case uncheckedPatterns of
248− ([], _) ->
450+ [] ->
249− True
451+ Right checkedRows
254− (_, firstPattern : patterns) ->
453+ pattern@(A.At region _) : rest ->
255− case firstPattern of
454+ let nextRow = [simplify pattern] in
256− Ctor name args ->
455+ if isUseful checkedRows nextRow then
257− let
456+ toSimplifiedUsefulRows overallRegion (nextRow : checkedRows) rest
258− newMatrix =
457+ else
259− Maybe.mapMaybe (specialize name (length args)) matrix
458+ Left (Redundant overallRegion region (length checkedRows + 1))
261− isUseful arityDict newMatrix (args ++ patterns)
264− case isComplete arityDict matrix of
266− isUseful arityDict (Maybe.mapMaybe toDefault matrix) patterns
270− isUsefulHelp (ctor, arity) =
273− (Maybe.mapMaybe (specialize ctor arity) matrix)
274− (replicate arity Anything ++ patterns)
276− any isUsefulHelp arityInfo
281− Maybe.mapMaybe (specializeLiteral literal) matrix
283− isUseful arityDict newMatrix patterns
461+-- Check if a new row "vector" is useful given previous rows "matrix"
462+isUseful :: [[Pattern]] -> [Pattern] -> Bool
463+isUseful matrix vector =
466+ -- No rows are the same as the new vector! The vector is useful!
472+ -- There is nothing left in the new vector, but we still have
473+ -- rows that match the same things. This is not a useful vector!
476+ firstPattern : patterns ->
477+ case firstPattern of
479+ -- keep checking rows that start with this Ctor or Anything
481+ (Maybe.mapMaybe (specializeRowByCtor name (length args)) matrix)
485+ -- check if all alts appear in matrix
486+ case isComplete matrix of
488+ -- This Anything is useful because some Ctors are missing.
489+ -- But what if a previous row has an Anything?
490+ -- If so, this one is not useful.
491+ isUseful (Maybe.mapMaybe specializeRowByAnything matrix) patterns
494+ -- All Ctors are covered, so this Anything is not needed for any
495+ -- of those. But what if some of those Ctors have subpatterns
496+ -- that make them less general? If so, this actually is useful!
498+ isUsefulAlt (Can.CtorAlt ctor arity) =
500+ (Maybe.mapMaybe (specializeRowByCtor ctor arity) matrix)
501+ (replicate arity Anything ++ patterns)
503+ any isUsefulAlt alts
506+ -- keep checking rows that start with this Literal or Anything
508+ (Maybe.mapMaybe (specializeRowByLiteral literal) matrix)
512+-- INVARIANT: (length row == N) ==> (length result == arity + N - 1)
513+specializeRowByCtor :: N.Name -> Int -> [Pattern] -> Maybe [Pattern]
514+specializeRowByCtor ctorName arity row =
516+ Ctor _ name args : patterns ->
517+ if name == ctorName then
518+ Just (args ++ patterns)
287−-- SPECIALIZE MATRICES
522+ Anything : patterns ->
523+ Just (replicate arity Anything ++ patterns)
527+ "Compiler bug! After type checking, constructors and literals\
528+ \ should never align in pattern match exhaustiveness checks."
290−specialize :: Var.Canonical -> Int -> [Pattern] -> Maybe [Pattern]
291−specialize ctorName arity row =
294 error "Compiler error! Empty matrices should not get specialized."
531 error "Compiler error! Empty matrices should not get specialized."
296− firstPattern : patterns ->
297− case firstPattern of
299− if name == ctorName then Just (args ++ patterns) else Nothing
301− Anything ->
534+-- INVARIANT: (length row == N) ==> (length result == N-1)
535+specializeRowByLiteral :: Literal -> [Pattern] -> Maybe [Pattern]
536+specializeRowByLiteral literal row =
538+ Literal lit : patterns ->
539+ if lit == literal then
302− Just (replicate arity Anything ++ patterns)
540+ Just patterns
304− Literal _ ->
544+ Anything : patterns ->
305− error $
545+ Just patterns
306− "Compiler bug! After type checking, constructors and literals\
307− \ should never align in pattern match exhaustiveness checks."
549+ "Compiler bug! After type checking, constructors and literals\
550+ \ should never align in pattern match exhaustiveness checks."
310−specializeLiteral :: L.Literal -> [Pattern] -> Maybe [Pattern]
311−specializeLiteral literal row =
314 error "Compiler error! Empty matrices should not get specialized."
553 error "Compiler error! Empty matrices should not get specialized."
316− firstPattern : patterns ->
317− case firstPattern of
319− if lit == literal then Just patterns else Nothing
326− "Compiler bug! After type checking, constructors and literals\
327− \ should never align in pattern match exhaustiveness checks."
329−
556+-- INVARIANT: (length row == N) ==> (length result == N-1)
330−toDefault :: [Pattern] -> Maybe [Pattern]
557+specializeRowByAnything :: [Pattern] -> Maybe [Pattern]
331−toDefault row =
558+specializeRowByAnything row =
332 case row of
559 case row of
336− Ctor _ _ : _ ->
563+ Ctor _ _ _ : _ ->
339 Anything : patterns ->
566 Anything : patterns ->
@@ -347,108 +574,38 @@ toDefault row =
347-- ALL CONSTRUCTORS ARE PRESENT?
574-- ALL CONSTRUCTORS ARE PRESENT?
350−isComplete :: ArityDict -> [[Pattern]] -> Maybe [(Var.Canonical, Int)]
577+data Complete
351−isComplete arityDict matrix =
578+ = Yes [Can.CtorAlt]
354− List.foldl' isCompleteHelp Set.empty matrix
364− (ArityInfo expected info) =
365− getArityInfo (Set.findMin ctorSet) arityDict
367− if expected == actual then Just info else Nothing
370−isCompleteHelp :: Set.Set Var.Canonical -> [Pattern] -> Set.Set Var.Canonical
371−isCompleteHelp ctors row =
374− Set.insert name ctors
385− Map.Map Var.Home (Map.Map Text ArityInfo)
391− , _info :: [(Var.Canonical, Int)]
395−toArityDict :: Module.Interfaces -> ModuleName.Canonical -> Module.Unions -> ArityDict
582+isComplete :: [[Pattern]] -> Complete
396−toArityDict interfaces localName localUnions =
583+isComplete matrix =
398− |> Map.mapWithKey (\name iface -> toArityEntry name (Module.iUnions iface))
399− |> Map.mapKeysMonotonic Var.Module
400− |> Map.insert (Var.Module localName) (toArityEntry localName localUnions)
401− |> Map.union builtinDict
404−builtinDict :: ArityDict
407− listInfo =
585+ ctors = collectCtors matrix
408− ArityInfo 2 [ (Var.cons, 2), (Var.nil, 0) ]
586+ numSeen = Map.size ctors
411− ArityInfo 2 [ (Var.true, 0), (Var.false, 0) ]
413− Map.singleton Var.BuiltIn $ Map.fromList $
588+ if numSeen == 0 then
414− [ ("::", listInfo)
589+ No
415− , ("[]", listInfo)
590+ else
416− , ("True", boolInfo)
591+ let (Can.CtorAlts numAlts alts) = snd (Map.findMin ctors) in
417− , ("False", boolInfo)
592+ if numSeen == numAlts then Yes alts else No
419− ++ map makeTupleInfo [0..8]
422−makeTupleInfo :: Int -> ( Text, ArityInfo )
424− ( Help.makeTuple n, ArityInfo 1 [ (Var.tuple n, n) ] )
427−toArityEntry :: ModuleName.Canonical -> Module.Unions -> Map.Map Text ArityInfo
428−toArityEntry name unions =
429− Map.fromList (concatMap (toArityEntryHelp name) (Map.elems unions))
599+collectCtors :: [[Pattern]] -> Map.Map N.Name Can.CtorAlts
600+collectCtors matrix =
601+ List.foldl' collectCtorsHelp Map.empty matrix
432−toArityEntryHelp :: ModuleName.Canonical -> Module.UnionInfo Text -> [(Text, ArityInfo)]
433−toArityEntryHelp name (_tvars, ctors) =
436− ArityInfo (length ctors) (map (Var.fromModule name *** length) ctors)
438− map (second (\_ -> arityInfo)) ctors
604+collectCtorsHelp :: Map.Map N.Name Can.CtorAlts -> [Pattern] -> Map.Map N.Name Can.CtorAlts
605+collectCtorsHelp ctors row =
607+ Ctor info name _ : _ ->
608+ Map.insert name info ctors
441−getArityInfo :: Var.Canonical -> ArityDict -> ArityInfo
610+ _ ->
442−getArityInfo var@(Var.Canonical home name) arityDict =
611+ ctors
443− case Map.lookup name =<< Map.lookup home arityDict of
448− if Help.isTuple name then
449− ArityInfo 1 [ (var, read (drop 6 (Text.unpack name))) ]
452− "Since the Nitpick phase happens after canonicalization and type\
453− \ inference, it is impossible that a pattern in a case cannot be\