on master help sign in
Turn incomplete pattern matches into a static error
99a44a0Evan Czaplicki
elm-compiler.cabal+1
@@ -103,6 +103,7 @@ Library
103 Reporting.Error.Canonicalize,
103 Reporting.Error.Canonicalize,
104 Reporting.Error.Docs,
104 Reporting.Error.Docs,
105 Reporting.Error.Helpers,
105 Reporting.Error.Helpers,
106+ Reporting.Error.Pattern,
106 Reporting.Error.Syntax,
107 Reporting.Error.Syntax,
107 Reporting.Error.Type,
108 Reporting.Error.Type,
108 Reporting.PrettyPrint,
109 Reporting.PrettyPrint,
src/Compile.hs+2−1
@@ -45,7 +45,8 @@ compile packageName isRoot canonicalImports interfaces source =
45 Result.mapError Error.Type $
45 Result.mapError Error.Type $
46 Nitpick.topLevelTypes types (Module.body validModule)
46 Nitpick.topLevelTypes types (Module.body validModule)
47
47
48+ Result.mapError Error.Pattern $
48− Nitpick.patternMatches interfaces canonicalModule
49+ Nitpick.patternMatches interfaces canonicalModule
49
50
50 -- Add the real list of types
51 -- Add the real list of types
51 let body = (Module.body canonicalModule) { Module.types = types }
52 let body = (Module.body canonicalModule) { Module.types = types }
src/Nitpick/PatternMatches.hs+9−9
@@ -17,15 +17,15 @@ import qualified AST.Variable as Var
17import Elm.Utils ((|>))
17import Elm.Utils ((|>))
18import Nitpick.Pattern (Pattern(..), fromCanonicalPattern)
18import Nitpick.Pattern (Pattern(..), fromCanonicalPattern)
19import qualified Reporting.Annotation as A
19import qualified Reporting.Annotation as A
20+import qualified Reporting.Error.Pattern as Error
20import qualified Reporting.Region as Region
21import qualified Reporting.Region as Region
21import qualified Reporting.Result as Result
22import qualified Reporting.Result as Result
22−import qualified Reporting.Warning as Warning
23
23
24
24
25patternMatches
25patternMatches
26 :: Module.Interfaces
26 :: Module.Interfaces
27 -> Module.CanonicalModule
27 -> Module.CanonicalModule
28− -> Result.Result Warning.Warning e ()
28+ -> Result.Result w Error.Error ()
29patternMatches interfaces modul =
29patternMatches interfaces modul =
30 let
30 let
31 name = Module.name modul
31 name = Module.name modul
@@ -102,7 +102,7 @@ lookupOtherTags (Var.Canonical home name) tagDict =
102checkExpression
102checkExpression
103 :: TagDict
103 :: TagDict
104 -> Canonical.Expr
104 -> Canonical.Expr
105− -> Result.Result Warning.Warning e ()
105+ -> Result.Result w Error.Error ()
106checkExpression tagDict (A.A region expression) =
106checkExpression tagDict (A.A region expression) =
107 let
107 let
108 go =
108 go =
@@ -188,7 +188,7 @@ checkPatterns
188 :: TagDict
188 :: TagDict
189 -> Region.Region
189 -> Region.Region
190 -> [Pattern.CanonicalPattern]
190 -> [Pattern.CanonicalPattern]
191− -> Result.Result Warning.Warning e ()
191+ -> Result.Result w Error.Error ()
192checkPatterns tagDict region patterns =
192checkPatterns tagDict region patterns =
193 checkPatternsHelp tagDict region [Anything] patterns
193 checkPatternsHelp tagDict region [Anything] patterns
194
194
@@ -198,14 +198,14 @@ checkPatternsHelp
198 -> Region.Region
198 -> Region.Region
199 -> [Pattern]
199 -> [Pattern]
200 -> [Pattern.CanonicalPattern]
200 -> [Pattern.CanonicalPattern]
201− -> Result.Result Warning.Warning e ()
201+ -> Result.Result w Error.Error ()
202checkPatternsHelp tagDict region unhandled patterns =
202checkPatternsHelp tagDict region unhandled patterns =
203 case (unhandled, patterns) of
203 case (unhandled, patterns) of
204 ([], []) ->
204 ([], []) ->
205 return ()
205 return ()
206
206
207 (_:_, []) ->
207 (_:_, []) ->
208− Result.warn region (Warning.InexhaustivePatternMatch unhandled)
208+ Result.throw region (Error.Incomplete unhandled)
209
209
210 (_, pattern@(A.A localRegion _) : remainingPatterns) ->
210 (_, pattern@(A.A localRegion _) : remainingPatterns) ->
211 do newUnhandled <- filterPatterns tagDict localRegion pattern unhandled
211 do newUnhandled <- filterPatterns tagDict localRegion pattern unhandled
@@ -217,7 +217,7 @@ filterPatterns
217 -> Region.Region
217 -> Region.Region
218 -> Pattern.CanonicalPattern
218 -> Pattern.CanonicalPattern
219 -> [Pattern]
219 -> [Pattern]
220− -> Result.Result Warning.Warning e [Pattern]
220+ -> Result.Result w Error.Error [Pattern]
221filterPatterns tagDict region pattern unhandled =
221filterPatterns tagDict region pattern unhandled =
222 let
222 let
223 nitPattern =
223 nitPattern =
@@ -227,8 +227,8 @@ filterPatterns tagDict region pattern unhandled =
227 intersection pat nitPattern == Nothing
227 intersection pat nitPattern == Nothing
228 in
228 in
229 if all noIntersection unhandled then
229 if all noIntersection unhandled then
230− do Result.warn region Warning.RedundantPatternMatch
230+ Result.throw region Error.Redundant
231− return unhandled
231+
232 else
232 else
233 do let complementPatterns = complement tagDict nitPattern
233 do let complementPatterns = complement tagDict nitPattern
234 return $
234 return $
src/Reporting/Error.hs+8
@@ -9,6 +9,7 @@ import Prelude hiding (print)
9import qualified Reporting.Annotation as A
9import qualified Reporting.Annotation as A
10import qualified Reporting.Error.Canonicalize as Canonicalize
10import qualified Reporting.Error.Canonicalize as Canonicalize
11import qualified Reporting.Error.Docs as Docs
11import qualified Reporting.Error.Docs as Docs
12+import qualified Reporting.Error.Pattern as Pattern
12import qualified Reporting.Error.Syntax as Syntax
13import qualified Reporting.Error.Syntax as Syntax
13import qualified Reporting.Error.Type as Type
14import qualified Reporting.Error.Type as Type
14import qualified Reporting.PrettyPrint as P
15import qualified Reporting.PrettyPrint as P
@@ -21,6 +22,7 @@ data Error
21 = Syntax Syntax.Error
22 = Syntax Syntax.Error
22 | Canonicalize Canonicalize.Error
23 | Canonicalize Canonicalize.Error
23 | Type Type.Error
24 | Type Type.Error
25+ | Pattern Pattern.Error
24 | Docs Docs.Error
26 | Docs Docs.Error
25
27
26
28
@@ -38,6 +40,9 @@ toReport dealiaser err =
38 Type typeError ->
40 Type typeError ->
39 Type.toReport dealiaser typeError
41 Type.toReport dealiaser typeError
40
42
43+ Pattern patternError ->
44+ Pattern.toReport dealiaser patternError
45+
41 Docs docsError ->
46 Docs docsError ->
42 Docs.toReport docsError
47 Docs.toReport docsError
43
48
@@ -76,6 +81,9 @@ toJson dealiaser filePath (A.A region err) =
76 Type typeError ->
81 Type typeError ->
77 Report.toJson [] (Type.toReport dealiaser typeError)
82 Report.toJson [] (Type.toReport dealiaser typeError)
78
83
84+ Pattern patternError ->
85+ Report.toJson [] (Pattern.toReport dealiaser patternError)
86+
79 Docs docsError ->
87 Docs docsError ->
80 Report.toJson [] (Docs.toReport docsError)
88 Report.toJson [] (Docs.toReport docsError)
81 in
89 in
src/Reporting/Error/Pattern.hsnew file+69
@@ -0,0 +1,69 @@
1+{-# OPTIONS_GHC -Wall #-}
2+module Reporting.Error.Pattern where
3+
4+import qualified Data.Char as Char
5+import Text.PrettyPrint ((<+>))
6+import qualified Text.PrettyPrint as P
7+
8+import qualified AST.Helpers as Help
9+import qualified AST.Type as Type
10+import qualified AST.Variable as Var
11+import qualified Nitpick.Pattern as Pattern
12+import qualified Reporting.PrettyPrint as P
13+import qualified Reporting.Region as Region
14+import qualified Reporting.Report as Report
15+
16+
17+data Error
18+ = Incomplete [Pattern.Pattern]
19+ | Redundant
20+
21+
22+-- TO REPORT
23+
24+toReport :: P.Dealiaser -> Error -> Report.Report
25+toReport dealiaser err =
26+ case err of
27+ Incomplete unhandled ->
28+ Report.simple
29+ "MISSING PATTERN"
30+ "The following `case` does not handle all possible inputs."
31+ ( "The following patterns are not being handled:\n"
32+ ++ viewPatternList unhandled
33+ ++ "\n\n"
34+ ++ "Did you add a new tag to a union type? Add how it should be handled here and"
35+ ++ "you will be all set!"
36+ ++ ""
37+ ++ "If you want to go quick and implement this later, you can end the pattern with:"
38+ ++ ""
39+ ++ " _ ->"
40+ ++ " Debug.crash \"TODO\""
41+ ++ ""
42+ ++ "When I see cases that end with this pattern, I augment the runtime error with"
43+ ++ "the module name, the relevant line numbers, and the value led down that branch."
44+ ++ ""
45+ ++ "That said, you almost never want this in production code. If you think there"
46+ ++ "are certain cases that are impossible in practice, consider refining how you"
47+ ++ "model the data. If it can never happen in practice, you can probably define a"
48+ ++ "union type such that the \"impossible\" cannot even be constructed in the"
49+ ++ "first place. That is how you make something \"impossible\" truly impossible."
50+ ++ ""
51+ ++ "I won't say \"I told you so\" in the runtime error, but I'll be thinking it ;)"
52+ )
53+
54+ Redundant ->
55+ Report.simple
56+ "REDUNDANT PATTERN"
57+ "The following pattern is redundant."
58+ "Any value with this shape will be handled by a previous pattern."
59+
60+
61+viewPatternList :: [Pattern.Pattern] -> String
62+viewPatternList unhandledPatterns =
63+ let
64+ (showPatterns, rest) =
65+ splitAt 4 unhandledPatterns
66+ in
67+ concatMap ((++) "\n ") $
68+ map Pattern.toString showPatterns
69+ ++ if null rest then [] else ["..."]
src/Reporting/Warning.hs−42
@@ -9,7 +9,6 @@ import Text.PrettyPrint ((<+>))
9
9
10import qualified AST.Module.Name as ModuleName
10import qualified AST.Module.Name as ModuleName
11import qualified AST.Type as Type
11import qualified AST.Type as Type
12−import qualified Nitpick.Pattern as Pattern
13import qualified Reporting.Annotation as A
12import qualified Reporting.Annotation as A
14import qualified Reporting.PrettyPrint as P
13import qualified Reporting.PrettyPrint as P
15import qualified Reporting.Report as Report
14import qualified Reporting.Report as Report
@@ -20,8 +19,6 @@ import qualified Reporting.Report as Report
20data Warning
19data Warning
21 = UnusedImport ModuleName.Raw
20 = UnusedImport ModuleName.Raw
22 | MissingTypeAnnotation String Type.Canonical
21 | MissingTypeAnnotation String Type.Canonical
23− | InexhaustivePatternMatch [Pattern.Pattern]
24− | RedundantPatternMatch
25
22
26
23
27-- TO STRING
24-- TO STRING
@@ -59,32 +56,6 @@ toReport dealiaser warning =
59 4
56 4
60 (P.pretty dealiaser False inferredType)
57 (P.pretty dealiaser False inferredType)
61
58
62− InexhaustivePatternMatch unhandled ->
63− Report.simple
64− "missing pattern"
65− "The following case expression does not handle all possible inputs."
66− ( "The following patterns are not being handled:\n"
67− ++ viewPatternList unhandled
68− ++ "\n\n"
69− ++ "If we get values like this, we have no choice but to crash! Normally this means\n"
70− ++ "you just added a new tag to a union type, but sometimes it can be because there\n"
71− ++ "is something very odd about the data you are modeling. In those rare cases, you\n"
72− ++ "probably want to either:\n"
73− ++ "\n"
74− ++ " 1. Rethink the model. Maybe some cases are \"impossible\" but it is still\n"
75− ++ " possible to construct such cases. Is there a way to model the values more\n"
76− ++ " precisely such that \"impossible\" values truly are impossible?\n"
77− ++ " 2. End the pattern match with a wildcard match that leads to an expression\n"
78− ++ " like: Debug.crash \"If you are reading this, I have made a mistake!\"\n"
79− ++ " Generally speaking, you do not want it to have to be this way."
80− )
81−
82− RedundantPatternMatch ->
83− Report.simple
84− "redundant pattern"
85− "The following pattern is redundant."
86− "Any value with this shape will be handled by a previous pattern."
87−
88
59
89-- TO JSON
60-- TO JSON
90
61
@@ -100,16 +71,3 @@ toJson dealiaser filePath (A.A region warning) =
100 , "type" .= ("warning" :: String)
71 , "type" .= ("warning" :: String)
101 ]
72 ]
102 ++ additionalFields
73 ++ additionalFields
103−
104−
105−-- PATTERN WARNINGS
106−
107−viewPatternList :: [Pattern.Pattern] -> String
108−viewPatternList unhandledPatterns =
109− let
110− (showPatterns, rest) =
111− splitAt 4 unhandledPatterns
112− in
113− concatMap ((++) "\n ") $
114− map Pattern.toString showPatterns
115− ++ if null rest then [] else ["..."]