11module Nitpick.PatternMatchesSpec (spec ) where
22
33import AST.Canonical qualified as Can
4- import AST.Source qualified as Src
4+ import AST.Canonical ( Pattern_ )
55import Data.Index qualified as Index
6+ import Data.List (intercalate )
67import Data.Map qualified as Map
78import Data.Name qualified as N
8- import Data.NonEmptyList qualified as NE
99import Data.Utf8 qualified as Utf8
1010import Gren.ModuleName qualified as ModuleName
1111import Gren.Package qualified as Pkg
1212import Reporting.Annotation qualified as A
1313
14- import Nitpick.PatternMatches (check , Error (.. ))
14+ import Nitpick.PatternMatches (Pattern (.. ), Literal (.. ), Context (.. ), Error (.. ),
15+ checkPatterns )
1516
16- import Test.Hspec (Spec , describe , it )
17+ import Test.Hspec (Spec , describe , it , shouldBe )
18+
19+ -- Create a Pkg.Name
20+ packageName :: String -> String -> Pkg. Name
21+ packageName pkgName authorName =
22+ Pkg. Name
23+ { Pkg. _author = Utf8. fromChars authorName
24+ , Pkg. _project = Utf8. fromChars pkgName
25+ }
26+
27+ -- Create a ModuleName.Canonical
28+ moduleNameCanonical :: String -> String -> String -> ModuleName. Canonical
29+ moduleNameCanonical pkgName authorName modName =
30+ ModuleName. Canonical
31+ { ModuleName. _package = packageName pkgName authorName
32+ , ModuleName. _module = N. fromChars modName
33+ }
1734
1835-- Create a Can.Union for Bool
1936boolUnion :: Can. Union
@@ -28,124 +45,224 @@ boolUnion =
2845 , Can. _u_opts = Can. Enum
2946 }
3047
31- emptyUnions :: Map. Map N. Name Can. Union
32- emptyUnions = Map. empty
48+ -- Create a Can.Union for Maybe
49+ maybeUnion :: Can. Union
50+ maybeUnion =
51+ Can. Union
52+ { Can. _u_vars = [(N. fromChars " a" )]
53+ , Can. _u_alts =
54+ [ Can. Ctor (N. fromChars " Just" ) (Index. first) 1 [Can. TVar (N. fromChars " a" )]
55+ , Can. Ctor (N. fromChars " Nothing" ) (Index. next (Index. first)) 0 []
56+ ]
57+ , Can. _u_numAlts = 2
58+ , Can. _u_opts = Can. Normal
59+ }
3360
34- emptyAliases :: Map. Map N. Name Can. Alias
35- emptyAliases = Map. empty
61+ -- Create a Pattern_ that is a PCtor for Maybe
62+ maybePCtor :: Bool -> [Can. PatternCtorArg ] -> Pattern_
63+ maybePCtor isJust args =
64+ Can. PCtor
65+ { Can. _p_home = moduleNameCanonical " core" " gren-lang" " Maybe"
66+ , Can. _p_type = (N. fromChars " Maybe" )
67+ , Can. _p_union = maybeUnion
68+ , Can. _p_name = if isJust then (N. fromChars " Just" ) else (N. fromChars " Nothing" )
69+ , Can. _p_index = if isJust then Index. first else (Index. next (Index. first))
70+ , Can. _p_args = args
71+ }
3672
37- emptyBinops :: Map. Map N. Name Can. Binop
38- emptyBinops = Map. empty
3973
40- packageName :: String -> String -> Pkg. Name
41- packageName pkgName authorName =
42- Pkg. Name
43- { Pkg. _author = Utf8. fromChars authorName
44- , Pkg. _project = Utf8. fromChars pkgName
45- }
74+ {-
75+ Test 1: Incomplete Bool Records
4676
47- moduleNameCanonical :: String -> String -> String -> ModuleName. Canonical
48- moduleNameCanonical pkgName authorName modName =
49- ModuleName. Canonical
50- { ModuleName. _package = packageName pkgName authorName
51- , ModuleName. _module = N. fromChars modName
52- }
77+ This is the original case reported by marias. It exercises
78+ a bug in the compiler.
5379
54- -- Create a Module from Decls
55- -- We use A.zero to give an empty Region for exports and docs, as we don't
56- -- care about their values
57- makeModule :: Can. Decls -> Can. Module
58- makeModule decls =
59- Can. Module
60- { Can. _name = moduleNameCanonical " TestPkg" " gren-devs" " TestModule"
61- , Can. _exports = Can. ExportEverything A. zero
62- , Can. _docs = Src. NoDocs A. zero
63- , Can. _decls = decls
64- , Can. _unions = emptyUnions
65- , Can. _aliases = emptyAliases
66- , Can. _binops = emptyBinops
67- , Can. _effects = Can. NoEffects
68- }
80+ fn r =
81+ when r is
82+ { a = False, b = True } -> 1
83+ { a = True, b = False } -> 2
84+ -}
85+
86+ -- These are the Can.Patterns produced by the parser and checkCases
87+ test1InputCanPatterns :: [Can. Pattern ]
88+ test1InputCanPatterns =
89+ [ (A. At A. zero (Can. PRecord
90+ [ A. At A. zero (Can. PRFieldPattern (N. fromChars " a" ) (A. At A. zero (Can. PBool boolUnion False )))
91+ , A. At A. zero (Can. PRFieldPattern (N. fromChars " b" ) (A. At A. zero (Can. PBool boolUnion True )))
92+ ]))
93+ , (A. At A. zero (Can. PRecord
94+ [ A. At A. zero (Can. PRFieldPattern (N. fromChars " a" ) (A. At A. zero (Can. PBool boolUnion True )))
95+ , A. At A. zero (Can. PRFieldPattern (N. fromChars " b" ) (A. At A. zero (Can. PBool boolUnion False )))
96+ ]))
97+ ]
98+
99+ -- We expect isExhaustive to find these patterns as missing:
100+ -- It finds 1 Error, which has 2 strings, one for each missing pattern
101+ test1Expectation :: [[String ]]
102+ test1Expectation =
103+ [
104+ [ " a : True, b : True"
105+ , " a : False, b : False"
106+ ]
107+ ]
69108
70- -- In the unit test we may need to induce a failure just to appease
71- -- the compiler. Use this Region when doing so.
72- failedRegion :: A. Region
73- failedRegion =
74- A. Region (A. Position 99 99 ) (A. Position 99 99 )
75109
76110
77- -- Incomplete Bool Records
78111{-
112+ Test 2: Record destructuring, as seen in core.git's Dict.gren
113+
114+ After an incorrect fix for test1 by gilbertr, this case was found
115+ to need extra handling. It *is* exhaustive, but the incorrect fix
116+ found it to be non-exhaustive.
117+
79118fn r =
80119 when r is
81- { a = False, b = True } -> 1
82- { a = True, b = False } -> 2
120+ Nothing -> "Nothing"
121+ Just { first = { key = lKey, value = lValue }, rest } -> lKey
83122-}
84- -- The AST
85- -- Debug.Trace trace was used to show the decls during "check",
86- -- and this function was entered into "gren repl".
87- -- The result was used to create this AST
88- --
89- -- Since we don't care about the true column/row range of each token
90- -- in the source code, we use A.zero for each Region.
91- incompleteBoolRecordsDecls :: Can. Decls
92- incompleteBoolRecordsDecls =
93- Can. Declare
94- (Can. Def (A. At A. zero (N. fromChars " fn" ))
95- [ A. At A. zero (Can. PVar (N. fromChars " r" )) ]
96- (A. At A. zero
97- (Can. Case (A. At A. zero (Can. VarLocal (N. fromChars " r" )))
98- [ Can. CaseBranch
99- (A. At A. zero
100- (Can. PRecord
101- [ A. At A. zero (Can. PRFieldPattern (N. fromChars " a" ) (A. At A. zero (Can. PBool boolUnion False )))
102- , A. At A. zero (Can. PRFieldPattern (N. fromChars " b" ) (A. At A. zero (Can. PBool boolUnion True )))
103- ]
104- )
105- )
106- (A. At A. zero (Can. Int 1 ))
107- , Can. CaseBranch
108- (A. At A. zero
109- (Can. PRecord
110- [ A. At A. zero (Can. PRFieldPattern (N. fromChars " a" ) (A. At A. zero (Can. PBool boolUnion True )))
111- , A. At A. zero (Can. PRFieldPattern (N. fromChars " b" ) (A. At A. zero (Can. PBool boolUnion False )))
112- ]
113- )
114- )
115- (A. At A. zero (Can. Int 2 ))
116- ]
117- )
123+
124+ -- These are the Can.Patterns produced by the parser and checkCases
125+ test2InputCanPatterns :: [Can. Pattern ]
126+ test2InputCanPatterns =
127+ [ (A. At A. zero (maybePCtor False [] ))
128+ , (A. At A. zero (maybePCtor True
129+ [ Can. PatternCtorArg
130+ { Can. _index = Index. first
131+ , Can. _type = Can. TVar (N. fromChars " a" )
132+ , Can. _arg = (A. At A. zero (
133+ Can. PRecord
134+ [ A. At A. zero (Can. PRFieldPattern (N. fromChars " first" ) (
135+ A. At A. zero (Can. PRecord
136+ [ A. At A. zero ( Can. PRFieldPattern (N. fromChars " key" )
137+ ( A. At A. zero ( Can. PVar (N. fromChars " lKey" ))))
138+ , A. At A. zero ( Can. PRFieldPattern (N. fromChars " value" )
139+ ( A. At A. zero ( Can. PVar (N. fromChars " lValue" ))))
140+ ])
141+ )
142+ )
143+ , A. At A. zero (Can. PRFieldPattern (N. fromChars " rest" ) (
144+ A. At A. zero (Can. PVar (N. fromChars " rest" ))
145+ )
146+ )
147+ ]
148+ ) -- Can.PRecord
149+ )
150+ }
151+ ] -- Can.PatternCtorArg
152+ )
118153 )
119- )
120- Can. SaveTheEnvironment
154+ ]
155+
156+ -- We expect isExhaustive to find *no* patterns to be missing:
157+ test2Expectation :: [[String ]]
158+ test2Expectation =
159+ []
160+
161+
162+ -- Small helper for the unit tests.
163+ -- This takes the input [Can.Pattern] and runs
164+ -- checkPatterns on it.
165+ runCheckPatterns :: [Can. Pattern ] -> [Error ]
166+ runCheckPatterns patterns =
167+ checkPatterns A. zero BadCase patterns []
168+
169+
170+ -- checkPatterns returns an [Error],
171+ -- which can have [Pattern] in it.
172+ -- This is cumbersome to test in the unit tests.
173+ -- We convert each [Error] (and thus, [Pattern]) to a String,
174+ -- making it a lot easier to assert on in the unit tests.
175+ --
176+ -- E.g., this [Error]
177+ -- [ Incomplete A.Region Context [Pattern] ]
178+ --
179+ -- in test 1 has this [Pattern]
180+ -- [ Record (fromList
181+ -- [("a",Ctor boolUnion "True" [])
182+ -- ,("b",Ctor boolUnion "True" [])
183+ -- ])
184+ -- , Record (fromList
185+ -- [("a",Ctor boolUnion "False" [])
186+ -- ,("b",Ctor boolUnion "False" [])
187+ -- ])
188+ -- ]
189+ --
190+ -- which we convert into:
191+ -- [
192+ -- [ "a: True, b: True",
193+ -- , "b: False, b: False"
194+ -- ]
195+ -- ]
196+
197+ -- Convert a list of Error into a matrix of Strings
198+ errorsToTestableStrings :: [Error ] -> [[String ]]
199+ errorsToTestableStrings errors =
200+ map (\ err ->
201+ case err of
202+ Incomplete _ _ patterns -> (map (\ pattern -> patternToTestableString pattern )) patterns
203+ Redundant _ _ _ -> [" redundant" ]
204+ ) errors
205+
206+ -- Given a list of Patterns, return a string representation
207+ patternsToTestableString :: [Pattern ] -> String
208+ patternsToTestableString patterns =
209+ " [ " ++ (intercalate " , " (map patternToTestableString patterns)) ++ " ]"
210+
211+ -- Convert a single Pattern into a String
212+ patternToTestableString :: Pattern -> String
213+ patternToTestableString pat =
214+ case pat of
215+ Anything -> " anything"
216+ Literal (Chr c) -> Utf8. toChars c
217+ Literal (Str s) -> Utf8. toChars s
218+ Literal (Int n) -> show n -- convert Int to String
219+
220+ -- Array patterns -> "[ " ++ (intercalate ", " (map patternToTestableString patterns)) ++ " ]"
221+ Array patterns -> patternsToTestableString patterns
222+
223+ Ctor _ vName patterns ->
224+ if null patterns
225+ then N. toChars vName
226+ else (N. toChars vName) ++ (patternsToTestableString patterns)
227+
121228
229+ Record patternMap ->
230+ let
231+ -- Make a new map with String values
232+ newValuesMap = Map. map (\ vPattern -> patternToTestableString vPattern) patternMap
122233
234+ -- Transform the keys into Strings
235+ -- (++) here is a combiner function in case of key collisions
236+ newMap = Map. mapKeysWith (++) (\ kName -> (N. toChars kName)) newValuesMap
237+
238+ -- Convert to sorted list of (key, value) pairs
239+ -- Maps are balanced trees in Haskell, so walking them gives us
240+ -- sorted already
241+ pairs = Map. toList newMap
242+
243+ -- Map each pair to a single "k : v" string
244+ formattedPairs = map (\ (k, v) -> k ++ " : " ++ v) pairs
245+
246+ in
247+ -- Join them all into one string
248+ intercalate " , " formattedPairs
249+
250+
251+
252+ -- The unit tests
123253spec :: Spec
124254spec = do
125255 describe " PatternMatches tests" $ do
256+ it " Test 1 bool matrix is not exhaustive" $ do
257+ let
258+ errors = runCheckPatterns test1InputCanPatterns
259+ errorStrings = errorsToTestableStrings errors
260+ in
261+ errorStrings `shouldBe` test1Expectation
126262
127- it " Incomplete bool matrix fails to compile " $ do
263+ it " Test 2 record destructruting is exhaustive " $ do
128264 let
129- -- result is: Either (NE.List Error) ()
130- result = check (makeModule incompleteBoolRecordsDecls)
265+ errors = runCheckPatterns test2InputCanPatterns
266+ errorStrings = errorsToTestableStrings errors
131267 in
132- case result of
133- Left neListError ->
134- let
135- err = case NE. toList neListError of
136- (x : _) -> x
137-
138- -- Impossible, as we are using NonEmptyList
139- -- Return something we know will fail the test
140- [] -> Redundant failedRegion failedRegion 99
141- in
142- case err of
143- -- Once we get the checker to return Incomplete,
144- -- mayb we can test the additional arguments to
145- -- "Incomplete"
146- Incomplete _ _ _ -> True
147- Redundant _ _ _ -> False
148-
149- Right () ->
150- -- The check succeeds, which is NOT what we want.
151- False
268+ errorStrings `shouldBe` test2Expectation
0 commit comments