Skip to content

Commit d96b0ef

Browse files
committed
Merge pull request purescript#1314 from nicodelpiano/redundancy3.1
Redundancy Checker
2 parents 0644a4c + 9ac688d commit d96b0ef

File tree

2 files changed

+50
-34
lines changed

2 files changed

+50
-34
lines changed

src/Language/PureScript/Errors.hs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ data SimpleErrorMessage
121121
| ShadowedName Ident
122122
| WildcardInferredType Type
123123
| NotExhaustivePattern [[Binder]] Bool
124+
| OverlappingPattern [[Binder]] Bool
124125
| ClassOperator ProperName Ident
125126
deriving (Show)
126127

@@ -231,6 +232,7 @@ errorCode em = case unwrapErrorMessage em of
231232
(ShadowedName _) -> "ShadowedName"
232233
(WildcardInferredType _) -> "WildcardInferredType"
233234
(NotExhaustivePattern _ _) -> "NotExhaustivePattern"
235+
(OverlappingPattern _ _) -> "OverlappingPattern"
234236
(ClassOperator _ _) -> "ClassOperator"
235237

236238
-- |
@@ -566,6 +568,11 @@ prettyPrintSingleError full level e = prettyPrintErrorMessage <$> onTypesInError
566568
, line $ "The definition has the following uncovered cases:\n"
567569
, Box.hsep 1 Box.left (map (paras . map (line . prettyPrintBinderAtom)) (transpose bs))
568570
] ++ if not b then [line "..."] else []
571+
goSimple (OverlappingPattern bs b) =
572+
indent $ paras $ [ line "Redundant cases have been detected."
573+
, line $ "The definition has the following redundant cases:\n"
574+
, Box.hsep 1 Box.left (map (paras . map (line . prettyPrintBinderAtom)) (transpose bs))
575+
] ++ if not b then [line "..."] else []
569576
go (NotYetDefined names err) =
570577
paras [ line $ "The following are not yet defined here: " ++ intercalate ", " (map show names) ++ ":"
571578
, indent $ go err

src/Language/PureScript/Linter/Exhaustive.hs

Lines changed: 43 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import Data.Function (on)
3131

3232
import Control.Monad (unless)
3333
import Control.Applicative
34-
import Control.Arrow (second)
34+
import Control.Arrow (first, second)
3535
import Control.Monad.Writer.Class
3636

3737
import Language.PureScript.AST.Binders
@@ -108,31 +108,30 @@ genericMerge f bsl@((s, b):bs) bsr@((s', b'):bs')
108108

109109
-- |
110110
-- Find the uncovered set between two binders:
111-
-- the first binder is the case we are trying to cover the second one is the matching binder
111+
-- the first binder is the case we are trying to cover, the second one is the matching binder
112112
--
113-
missingCasesSingle :: Environment -> ModuleName -> Binder -> Binder -> [Binder]
114-
missingCasesSingle _ _ _ NullBinder = []
115-
missingCasesSingle _ _ _ (VarBinder _) = []
113+
missingCasesSingle :: Environment -> ModuleName -> Binder -> Binder -> ([Binder], Maybe Bool)
114+
missingCasesSingle _ _ _ NullBinder = ([], Just True)
115+
missingCasesSingle _ _ _ (VarBinder _) = ([], Just True)
116116
missingCasesSingle env mn (VarBinder _) b = missingCasesSingle env mn NullBinder b
117117
missingCasesSingle env mn br (NamedBinder _ bl) = missingCasesSingle env mn br bl
118118
missingCasesSingle env mn NullBinder cb@(ConstructorBinder con _) =
119-
concatMap (\cp -> missingCasesSingle env mn cp cb) allPatterns
119+
(concatMap (\cp -> fst $ missingCasesSingle env mn cp cb) allPatterns, Just True)
120120
where
121121
allPatterns = map (\(p, t) -> ConstructorBinder (qualifyName p mn con) (initialize $ length t))
122122
$ getConstructors env mn con
123123
missingCasesSingle env mn cb@(ConstructorBinder con bs) (ConstructorBinder con' bs')
124-
| con == con' = map (ConstructorBinder con) (missingCasesMultiple env mn bs bs')
125-
| otherwise = [cb]
126-
missingCasesSingle _ _ NullBinder (ArrayBinder bs)
127-
| null bs = []
128-
| otherwise = []
124+
| con == con' = let (bs'', pr) = missingCasesMultiple env mn bs bs' in (map (ConstructorBinder con) bs'', pr)
125+
| otherwise = ([cb], Just False)
129126
missingCasesSingle env mn NullBinder (ObjectBinder bs) =
130-
map (ObjectBinder . zip (map fst bs)) allMisses
127+
(map (ObjectBinder . zip (map fst bs)) allMisses, pr)
131128
where
132-
allMisses = missingCasesMultiple env mn (initialize $ length bs) (map snd bs)
129+
(allMisses, pr) = missingCasesMultiple env mn (initialize $ length bs) (map snd bs)
133130
missingCasesSingle env mn (ObjectBinder bs) (ObjectBinder bs') =
134-
map (ObjectBinder . zip sortedNames) $ uncurry (missingCasesMultiple env mn) (unzip binders)
131+
(map (ObjectBinder . zip sortedNames) allMisses, pr)
135132
where
133+
(allMisses, pr) = uncurry (missingCasesMultiple env mn) (unzip binders)
134+
136135
sortNames = sortBy (compare `on` fst)
137136

138137
(sbs, sbs') = (sortNames bs, sortNames bs')
@@ -146,12 +145,12 @@ missingCasesSingle env mn (ObjectBinder bs) (ObjectBinder bs') =
146145
compBS e s b b' = (s, compB e b b')
147146

148147
(sortedNames, binders) = unzip $ genericMerge (compBS NullBinder) sbs sbs'
149-
missingCasesSingle _ _ NullBinder (BooleanBinder b) = [BooleanBinder $ not b]
148+
missingCasesSingle _ _ NullBinder (BooleanBinder b) = ([BooleanBinder $ not b], Just True)
150149
missingCasesSingle _ _ (BooleanBinder bl) (BooleanBinder br)
151-
| bl == br = []
152-
| otherwise = [BooleanBinder bl]
150+
| bl == br = ([], Just True)
151+
| otherwise = ([BooleanBinder bl], Just False)
153152
missingCasesSingle env mn b (PositionedBinder _ _ cb) = missingCasesSingle env mn b cb
154-
missingCasesSingle _ _ b _ = [b]
153+
missingCasesSingle _ _ b _ = ([b], Nothing)
155154

156155
-- |
157156
-- Returns the uncovered set of binders
@@ -179,15 +178,14 @@ missingCasesSingle _ _ b _ = [b]
179178
-- redundant or not, but uncovered at least. If we use `y` instead, we'll need to have a redundancy checker
180179
-- (which ought to be available soon), or increase the complexity of the algorithm.
181180
--
182-
missingCasesMultiple :: Environment -> ModuleName -> [Binder] -> [Binder] -> [[Binder]]
181+
missingCasesMultiple :: Environment -> ModuleName -> [Binder] -> [Binder] -> ([[Binder]], Maybe Bool)
183182
missingCasesMultiple env mn = go
184183
where
185-
go [] _ = []
186-
go (x:xs) (y:ys)
187-
| null miss = map (x :) (go xs ys)
188-
| otherwise = map (: xs) miss ++ map (x :) (go xs ys)
184+
go [] [] = ([], pure True)
185+
go (x:xs) (y:ys) = (map (: xs) miss1 ++ map (x :) miss2, liftA2 (&&) pr1 pr2)
189186
where
190-
miss = missingCasesSingle env mn x y
187+
(miss1, pr1) = missingCasesSingle env mn x y
188+
(miss2, pr2) = go xs ys
191189
go _ _ = error "Argument lengths did not match in missingCasesMultiple."
192190

193191
-- |
@@ -214,13 +212,15 @@ isExhaustiveGuard (Right _) = True
214212
-- |
215213
-- Returns the uncovered set of case alternatives
216214
--
217-
missingCases :: Environment -> ModuleName -> [Binder] -> CaseAlternative -> [[Binder]]
215+
missingCases :: Environment -> ModuleName -> [Binder] -> CaseAlternative -> ([[Binder]], Maybe Bool)
218216
missingCases env mn uncovered ca = missingCasesMultiple env mn uncovered (caseAlternativeBinders ca)
219217

220-
missingAlternative :: Environment -> ModuleName -> CaseAlternative -> [Binder] -> [[Binder]]
218+
missingAlternative :: Environment -> ModuleName -> CaseAlternative -> [Binder] -> ([[Binder]], Maybe Bool)
221219
missingAlternative env mn ca uncovered
222-
| isExhaustiveGuard (caseAlternativeResult ca) = missingCases env mn uncovered ca
223-
| otherwise = [uncovered]
220+
| isExhaustiveGuard (caseAlternativeResult ca) = mcases
221+
| otherwise = ([uncovered], snd mcases)
222+
where
223+
mcases = missingCases env mn uncovered ca
224224

225225
-- |
226226
-- Main exhaustivity checking function
@@ -229,20 +229,29 @@ missingAlternative env mn ca uncovered
229229
-- Then, returns the uncovered set of case alternatives.
230230
--
231231
checkExhaustive :: forall m. (MonadWriter MultipleErrors m) => Environment -> ModuleName -> [CaseAlternative] -> m ()
232-
checkExhaustive env mn cas = makeResult . nub $ foldl' step [initial] cas
232+
checkExhaustive env mn cas = makeResult . first nub $ foldl' step ([initial], (pure True, [])) cas
233233
where
234-
step :: [[Binder]] -> CaseAlternative -> [[Binder]]
235-
step uncovered ca = concatMap (missingAlternative env mn ca) uncovered
234+
step :: ([[Binder]], (Maybe Bool, [[Binder]])) -> CaseAlternative -> ([[Binder]], (Maybe Bool, [[Binder]]))
235+
step (uncovered, (nec, redundant)) ca =
236+
let (missed, pr) = unzip (map (missingAlternative env mn ca) uncovered)
237+
cond = or <$> sequenceA pr
238+
in (concat missed, (liftA2 (&&) cond nec,
239+
if fromMaybe True cond then redundant else caseAlternativeBinders ca : redundant))
240+
where
241+
sequenceA = foldr (liftA2 (:)) (pure [])
236242

237243
initial :: [Binder]
238244
initial = initialize numArgs
239245
where
240246
numArgs = length . caseAlternativeBinders . head $ cas
241247

242-
makeResult :: [[Binder]] -> m ()
243-
makeResult bss = unless (null bss) tellWarning
248+
makeResult :: ([[Binder]], (Maybe Bool, [[Binder]])) -> m ()
249+
makeResult (bss, (_, bss')) =
250+
do unless (null bss) tellExhaustive
251+
unless (null bss') tellRedundant
244252
where
245-
tellWarning = tell . errorMessage . uncurry NotExhaustivePattern . second null . splitAt 5 $ bss
253+
tellExhaustive = tell . errorMessage . uncurry NotExhaustivePattern . second null . splitAt 5 $ bss
254+
tellRedundant = tell . errorMessage . uncurry OverlappingPattern . second null . splitAt 5 $ bss'
246255

247256
-- |
248257
-- Exhaustivity checking over a list of declarations

0 commit comments

Comments
 (0)