1{-# OPTIONS_GHC -Wall #-}
2{-# LANGUAGE OverloadedStrings #-}
3module Nitpick.PatternMatches (patternMatches) where
5{- The algorithm used here comes from "Warnings for Pattern Matching"
6by Luc Maranget. Check it out for more information!
8http://moscova.inria.fr/~maranget/papers/warn/warn.pdf
12import Control.Arrow ((***), second)
13import qualified Data.Foldable as F
14import qualified Data.List as List
15import qualified Data.Map as Map
16import qualified Data.Maybe as Maybe
17import qualified Data.Set as Set
18import qualified Data.Text as Text
19import Data.Text (Text)
21import qualified AST.Expression.Canonical as C
22import qualified AST.Helpers as Help
23import qualified AST.Literal as L
24import qualified AST.Module as Module
25import qualified AST.Module.Name as ModuleName
26import qualified AST.Pattern as Pattern
27import qualified AST.Variable as Var
28import Elm.Utils ((|>))
29import Nitpick.Pattern (Pattern(..), fromCanonicalPattern)
30import qualified Optimize.DecisionTree as DT
31import qualified Reporting.Annotation as A
32import qualified Reporting.Error.Pattern as Error
33import qualified Reporting.Region as Region
34import qualified Reporting.Result as Result
38-- NITPICK PATTERN MATCHES
41type Result warning a =
42 Result.Result () warning Error.Error a
45patternMatches :: Module.Interfaces -> Module.Canonical -> Result wrn DT.VariantDict
46patternMatches interfaces (Module.Module name _ info) =
49 toArityDict interfaces name (Module.unions info)
52 Map.map (Map.map _size) arityDict
55 <* checkExpression arityDict (Module.program info)
58checkExpression :: ArityDict -> C.Expr -> Result wrn ()
59checkExpression arityDict (A.A region expression) =
62 checkExpression arityDict
75 F.traverse_ go listExprs
77 C.Binop _ leftExpr rightExpr ->
78 go2 leftExpr rightExpr
80 C.Lambda pattern@(A.A patRegion _) body ->
81 checkPatterns arityDict patRegion Error.Arg [pattern]
87 C.If branches finally ->
88 F.traverse_ (uncurry go2) branches
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 ->
101 <* checkPatterns arityDict region Error.Case (map fst branches)
102 <* F.traverse_ (go . snd) branches
104 C.Ctor _ctor exprs ->
107 C.Access record _field ->
110 C.Update record fields ->
112 <* F.traverse_ (go . snd) fields
115 F.traverse_ (go . snd) fields
123 C.OutgoingPort _ _ ->
126 C.IncomingPort _ _ ->
130 error "DANGER - Program AST nodes should not be in Nitpick.PatternMatches."
143checkPatterns :: ArityDict -> Region.Region -> Error.Origin -> [Pattern.Canonical] -> Result wrn ()
144checkPatterns arityDict region origin patterns =
145 do matrix <- checkRedundant arityDict region [] patterns
146 case isExhaustive arityDict matrix 1 of
151 Result.throw region (Error.Incomplete origin (map head badPatterns))
155-- EXHAUSTIVE PATTERNS
158isExhaustive :: ArityDict -> [[Pattern]] -> Int -> [[Pattern]]
159isExhaustive arityDict matrix n =
162 [replicate n Anything]
170 List.foldl' isCompleteHelp Set.empty matrix
177 <$> isExhaustive arityDict (Maybe.mapMaybe toDefault matrix) (n - 1)
181 (ArityInfo expected info) =
182 getArityInfo (Set.findMin ctorSet) arityDict
184 if actual < expected then
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
201isMissing :: Set.Set Var.Canonical -> (Var.Canonical, Int) -> Maybe Pattern
202isMissing ctorSet (name, arity) =
203 if Set.member name ctorSet then
207 Just (Ctor name (replicate arity Anything))
210recoverCtor :: Var.Canonical -> Int -> [Pattern] -> [Pattern]
211recoverCtor name arity patterns =
214 splitAt arity patterns
216 Ctor name args : rest
227 -> [Pattern.Canonical]
228 -> Result wrn [[Pattern]]
229checkRedundant 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))
245isUseful :: ArityDict -> [[Pattern]] -> [Pattern] -> Bool
246isUseful arityDict matrix vector =
247 case (matrix, vector) of
254 (_, firstPattern : patterns) ->
259 Maybe.mapMaybe (specialize name (length args)) matrix
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
287-- SPECIALIZE MATRICES
290specialize :: Var.Canonical -> Int -> [Pattern] -> Maybe [Pattern]
291specialize ctorName arity row =
294 error "Compiler error! Empty matrices should not get specialized."
296 firstPattern : patterns ->
299 if name == ctorName then Just (args ++ patterns) else Nothing
302 Just (replicate arity Anything ++ patterns)
306 "Compiler bug! After type checking, constructors and literals\
307 \ should never align in pattern match exhaustiveness checks."
310specializeLiteral :: L.Literal -> [Pattern] -> Maybe [Pattern]
311specializeLiteral literal row =
314 error "Compiler error! Empty matrices should not get specialized."
316 firstPattern : patterns ->
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."
330toDefault :: [Pattern] -> Maybe [Pattern]
339 Anything : patterns ->
347-- ALL CONSTRUCTORS ARE PRESENT?
350isComplete :: ArityDict -> [[Pattern]] -> Maybe [(Var.Canonical, Int)]
351isComplete arityDict matrix =
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
370isCompleteHelp :: Set.Set Var.Canonical -> [Pattern] -> Set.Set Var.Canonical
371isCompleteHelp ctors row =
374 Set.insert name ctors
385 Map.Map Var.Home (Map.Map Text ArityInfo)
391 , _info :: [(Var.Canonical, Int)]
395toArityDict :: Module.Interfaces -> ModuleName.Canonical -> Module.Unions -> ArityDict
396toArityDict interfaces localName localUnions =
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
404builtinDict :: ArityDict
408 ArityInfo 2 [ (Var.builtin "::", 2), (Var.builtin "[]", 0) ]
411 ArityInfo 2 [ (Var.builtin "True", 0), (Var.builtin "False", 0) ]
413 Map.singleton Var.BuiltIn $ Map.fromList $
417 , ("False", boolInfo)
419 ++ map makeTupleInfo [0..8]
422makeTupleInfo :: Int -> ( Text, ArityInfo )
426 Text.pack ("_Tuple" ++ show n)
428 ( name, ArityInfo 1 [ (Var.builtin name, n) ] )
431toArityEntry :: ModuleName.Canonical -> Module.Unions -> Map.Map Text ArityInfo
432toArityEntry name unions =
433 Map.fromList (concatMap (toArityEntryHelp name) (Map.elems unions))
436toArityEntryHelp :: ModuleName.Canonical -> Module.UnionInfo Text -> [(Text, ArityInfo)]
437toArityEntryHelp name (_tvars, ctors) =
440 ArityInfo (length ctors) (map (Var.fromModule name *** length) ctors)
442 map (second (\_ -> arityInfo)) ctors
445getArityInfo :: Var.Canonical -> ArityDict -> ArityInfo
446getArityInfo var@(Var.Canonical home name) arityDict =
447 case Map.lookup name =<< Map.lookup home arityDict of
452 if Help.isTuple name then
453 ArityInfo 1 [ (var, read (drop 6 (Text.unpack name))) ]
456 "Since the Nitpick phase happens after canonicalization and type\
457 \ inference, it is impossible that a pattern in a case cannot be\
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