summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler/deSugar/Check.hs137
-rw-r--r--testsuite/tests/pmcheck/should_compile/T15450.hs9
-rw-r--r--testsuite/tests/pmcheck/should_compile/T15450.stderr11
-rw-r--r--testsuite/tests/pmcheck/should_compile/all.T2
4 files changed, 119 insertions, 40 deletions
diff --git a/compiler/deSugar/Check.hs b/compiler/deSugar/Check.hs
index 8acb38b8d4..92edadb524 100644
--- a/compiler/deSugar/Check.hs
+++ b/compiler/deSugar/Check.hs
@@ -426,32 +426,28 @@ checkMatches' vars matches
-- for details.
checkEmptyCase' :: Id -> PmM PmResult
checkEmptyCase' var = do
- tm_css <- map toComplex . bagToList <$> liftD getTmCsDs
- case tmOracle initialTmState tm_css of
- Just tm_state -> do
- ty_css <- liftD getDictsDs
- fam_insts <- liftD dsGetFamInstEnvs
- mb_candidates <- inhabitationCandidates fam_insts (idType var)
- case mb_candidates of
- -- Inhabitation checking failed / the type is trivially inhabited
- Left ty -> return (uncoveredWithTy ty)
-
- -- A list of inhabitant candidates is available: Check for each
- -- one for the satisfiability of the constraints it gives rise to.
- Right candidates -> do
- missing_m <- flip concatMapM candidates $ \(va,tm_ct,ty_cs) -> do
- let all_ty_cs = unionBags ty_cs ty_css
- sat_ty <- tyOracle all_ty_cs
- return $ case (sat_ty, tmOracle tm_state (tm_ct:tm_css)) of
- (True, Just tm_state') -> [(va, all_ty_cs, tm_state')]
- _non_sat -> []
- let mkValVec (va,all_ty_cs,tm_state')
- = ValVec [va] (MkDelta all_ty_cs tm_state')
- uncovered = UncoveredPatterns (map mkValVec missing_m)
- return $ if null missing_m
- then emptyPmResult
- else PmResult FromBuiltin [] uncovered []
- Nothing -> return emptyPmResult
+ (tm_css, ty_css) <- pmInitialTmTyCs
+ fam_insts <- liftD dsGetFamInstEnvs
+ mb_candidates <- inhabitationCandidates fam_insts (idType var)
+ case mb_candidates of
+ -- Inhabitation checking failed / the type is trivially inhabited
+ Left ty -> return (uncoveredWithTy ty)
+
+ -- A list of inhabitant candidates is available: Check for each
+ -- one for the satisfiability of the constraints it gives rise to.
+ Right candidates -> do
+ missing_m <- flip concatMapM candidates $ \(va,tm_ct,ty_cs) -> do
+ mb_sat <- pmIsSatisfiable tm_ct tm_css ty_cs ty_css
+ pure $ case mb_sat of
+ Just (tm_state', all_ty_cs)
+ -> [(va, all_ty_cs, tm_state')]
+ Nothing -> []
+ let mkValVec (va,all_ty_cs,tm_state')
+ = ValVec [va] (MkDelta all_ty_cs tm_state')
+ uncovered = UncoveredPatterns (map mkValVec missing_m)
+ return $ if null missing_m
+ then emptyPmResult
+ else PmResult FromBuiltin [] uncovered []
-- | Returns 'True' if the argument 'Type' is a fully saturated application of
-- a closed type constructor.
@@ -543,6 +539,73 @@ pmTopNormaliseType_maybe env typ
Just (_co, rhs) -> NS_Step rec_nts rhs ((rhs:), id)
_ -> NS_Done
+-- | Determine suitable constraints to use at the beginning of pattern-match
+-- coverage checking by consulting the sets of term and type constraints
+-- currently in scope. If one of these sets of constraints is unsatisfiable,
+-- use an empty set in its place. (See
+-- @Note [Recovering from unsatisfiable pattern-matching constraints]@
+-- for why this is done.)
+pmInitialTmTyCs :: PmM (TmState, Bag EvVar)
+pmInitialTmTyCs = do
+ ty_cs <- liftD getDictsDs
+ tm_cs <- map toComplex . bagToList <$> liftD getTmCsDs
+ sat_ty <- tyOracle ty_cs
+ let initTyCs = if sat_ty then ty_cs else emptyBag
+ initTmState = fromMaybe initialTmState (tmOracle initialTmState tm_cs)
+ pure (initTmState, initTyCs)
+
+{-
+Note [Recovering from unsatisfiable pattern-matching constraints]
+~~~~~~~~~~~~~~~~
+Consider the following code (see #12957 and #15450):
+
+ f :: Int ~ Bool => ()
+ f = case True of { False -> () }
+
+We want to warn that the pattern-matching in `f` is non-exhaustive. But GHC
+used not to do this; in fact, it would warn that the match was /redundant/!
+This is because the constraint (Int ~ Bool) in `f` is unsatisfiable, and the
+coverage checker deems any matches with unsatifiable constraint sets to be
+unreachable.
+
+We decide to better than this. When beginning coverage checking, we first
+check if the constraints in scope are unsatisfiable, and if so, we start
+afresh with an empty set of constraints. This way, we'll get the warnings
+that we expect.
+-}
+
+-- | Given some term and type constraints, check if they are satisfiable.
+-- (In other words, this is the ⊢_Sat oracle judgment from the GADTs Meet
+-- Their Match paper.)
+--
+-- For the purposes of efficiency, this takes as separate arguments the
+-- ambient term and type constraints (which are known beforehand to be
+-- satisfiable), as well as the new term and type constraints (which may not
+-- be satisfiable). This lets us implement two mini-optimizations:
+--
+-- * If there are no new type constraints, then don't bother initializing
+-- the type oracle, since it's redundant to do so.
+-- * Since the new term constraint is a separate argument, we only need to
+-- execute one iteration of the term oracle (instead of traversing the
+-- entire set of term constraints).
+pmIsSatisfiable
+ :: ComplexEq -- ^ The new term constraint.
+ -> TmState -- ^ The ambient term constraints (known to be satisfiable).
+ -> Bag EvVar -- ^ The new type constraints.
+ -> Bag EvVar -- ^ The ambient type constraints (known to be satisfiable).
+ -> PmM (Maybe (TmState, Bag EvVar))
+ -- ^ @'Just' (term_cs, ty_cs)@ if the constraints are
+ -- satisfiable, where @term_cs@ and @ty_cs@ are the new sets of
+ -- term and type constraints, respectively. 'Nothing' otherwise.
+pmIsSatisfiable new_term_c amb_term_cs new_ty_cs amb_ty_cs = do
+ let ty_cs = new_ty_cs `unionBags` amb_ty_cs
+ sat_ty <- if isEmptyBag new_ty_cs
+ then pure True
+ else tyOracle ty_cs
+ pure $ case (sat_ty, solveOneEq amb_term_cs new_term_c) of
+ (True, Just term_cs) -> Just (term_cs, ty_cs)
+ _unsat -> Nothing
+
{- Note [Type normalisation for EmptyCase]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
EmptyCase is an exception for pattern matching, since it is strict. This means
@@ -1544,14 +1607,8 @@ runMany pm (m:ms) = mappend <$> pm m <*> runMany pm ms
-- delta with all term and type constraints in scope.
mkInitialUncovered :: [Id] -> PmM Uncovered
mkInitialUncovered vars = do
- ty_cs <- liftD getDictsDs
- tm_cs <- map toComplex . bagToList <$> liftD getTmCsDs
- sat_ty <- tyOracle ty_cs
- let initTyCs = if sat_ty then ty_cs else emptyBag
- initTmState = fromMaybe initialTmState (tmOracle initialTmState tm_cs)
- patterns = map PmVar vars
- -- If any of the term/type constraints are non
- -- satisfiable then return with the initialTmState. See #12957
+ (initTmState, initTyCs) <- pmInitialTmTyCs
+ let patterns = map PmVar vars
return [ValVec patterns (MkDelta initTyCs initTmState)]
-- | Increase the counter for elapsed algorithm iterations, check that the
@@ -1680,12 +1737,12 @@ pmcheckHd (p@(PmCon { pm_con_con = con, pm_con_arg_tys = tys }))
cons_cs <- mapM (liftD . mkOneConFull x) complete_match
inst_vsa <- flip concatMapM cons_cs $ \(va, tm_ct, ty_cs) -> do
- let ty_state = ty_cs `unionBags` delta_ty_cs delta -- not actually a state
- sat_ty <- if isEmptyBag ty_cs then return True
- else tyOracle ty_state
- return $ case (sat_ty, solveOneEq (delta_tm_cs delta) tm_ct) of
- (True, Just tm_state) -> [ValVec (va:vva) (MkDelta ty_state tm_state)]
- _ty_or_tm_failed -> []
+ mb_sat <- pmIsSatisfiable tm_ct (delta_tm_cs delta)
+ ty_cs (delta_ty_cs delta)
+ pure $ case mb_sat of
+ Just (tm_state, ty_state)
+ -> [ValVec (va:vva) (MkDelta ty_state tm_state)]
+ Nothing -> []
set_provenance prov .
force_if (canDiverge (idName x) (delta_tm_cs delta)) <$>
diff --git a/testsuite/tests/pmcheck/should_compile/T15450.hs b/testsuite/tests/pmcheck/should_compile/T15450.hs
new file mode 100644
index 0000000000..100c8e8f7e
--- /dev/null
+++ b/testsuite/tests/pmcheck/should_compile/T15450.hs
@@ -0,0 +1,9 @@
+{-# LANGUAGE EmptyCase #-}
+{-# LANGUAGE GADTs #-}
+module T15450 where
+
+f :: (Int ~ Bool) => Bool -> a
+f x = case x of {}
+
+g :: (Int ~ Bool) => Bool -> a
+g x = case x of True -> undefined
diff --git a/testsuite/tests/pmcheck/should_compile/T15450.stderr b/testsuite/tests/pmcheck/should_compile/T15450.stderr
new file mode 100644
index 0000000000..e9a320fb3c
--- /dev/null
+++ b/testsuite/tests/pmcheck/should_compile/T15450.stderr
@@ -0,0 +1,11 @@
+
+T15450.hs:6:7: warning: [-Wincomplete-patterns (in -Wextra)]
+ Pattern match(es) are non-exhaustive
+ In a case alternative:
+ Patterns not matched:
+ False
+ True
+
+T15450.hs:9:7: warning: [-Wincomplete-patterns (in -Wextra)]
+ Pattern match(es) are non-exhaustive
+ In a case alternative: Patterns not matched: False
diff --git a/testsuite/tests/pmcheck/should_compile/all.T b/testsuite/tests/pmcheck/should_compile/all.T
index 4030b0609a..acb2b7ff74 100644
--- a/testsuite/tests/pmcheck/should_compile/all.T
+++ b/testsuite/tests/pmcheck/should_compile/all.T
@@ -65,6 +65,8 @@ test('T14098', normal, compile,
['-fwarn-incomplete-patterns -fwarn-overlapping-patterns'])
test('T15385', normal, compile,
['-fwarn-incomplete-patterns -fwarn-overlapping-patterns'])
+test('T15450', normal, compile,
+ ['-fwarn-incomplete-patterns -fwarn-overlapping-patterns'])
# Other tests
test('pmc001', [], compile,