summaryrefslogtreecommitdiff
path: root/compiler
diff options
context:
space:
mode:
Diffstat (limited to 'compiler')
-rw-r--r--compiler/GHC/Builtin/Names.hs22
-rw-r--r--compiler/GHC/HsToCore/Pmc.hs10
-rw-r--r--compiler/GHC/HsToCore/Pmc/Check.hs81
3 files changed, 96 insertions, 17 deletions
diff --git a/compiler/GHC/Builtin/Names.hs b/compiler/GHC/Builtin/Names.hs
index 40af981264..2dc6e47493 100644
--- a/compiler/GHC/Builtin/Names.hs
+++ b/compiler/GHC/Builtin/Names.hs
@@ -319,6 +319,7 @@ basicKnownKeyNames
-- GHC Extensions
groupWithName,
+ considerAccessibleName,
-- Strings and lists
unpackCStringName, unpackCStringUtf8Name,
@@ -1122,8 +1123,9 @@ alternativeClassKey = mkPreludeMiscIdUnique 754
-- Functions for GHC extensions
-groupWithName :: Name
-groupWithName = varQual gHC_EXTS (fsLit "groupWith") groupWithIdKey
+groupWithName, considerAccessibleName :: Name
+groupWithName = varQual gHC_EXTS (fsLit "groupWith") groupWithIdKey
+considerAccessibleName = varQual gHC_EXTS (fsLit "considerAccessible") considerAccessibleIdKey
-- Random PrelBase functions
fromStringName, otherwiseIdName, foldrName, buildName, augmentName,
@@ -2362,15 +2364,13 @@ inlineIdKey, noinlineIdKey :: Unique
inlineIdKey = mkPreludeMiscIdUnique 120
-- see below
-mapIdKey, groupWithIdKey, dollarIdKey :: Unique
-mapIdKey = mkPreludeMiscIdUnique 121
-groupWithIdKey = mkPreludeMiscIdUnique 122
-dollarIdKey = mkPreludeMiscIdUnique 123
-
-coercionTokenIdKey :: Unique
-coercionTokenIdKey = mkPreludeMiscIdUnique 124
-
-noinlineIdKey = mkPreludeMiscIdUnique 125
+mapIdKey, groupWithIdKey, dollarIdKey, coercionTokenIdKey, considerAccessibleIdKey :: Unique
+mapIdKey = mkPreludeMiscIdUnique 121
+groupWithIdKey = mkPreludeMiscIdUnique 122
+dollarIdKey = mkPreludeMiscIdUnique 123
+coercionTokenIdKey = mkPreludeMiscIdUnique 124
+noinlineIdKey = mkPreludeMiscIdUnique 125
+considerAccessibleIdKey = mkPreludeMiscIdUnique 126
rationalToFloatIdKey, rationalToDoubleIdKey :: Unique
rationalToFloatIdKey = mkPreludeMiscIdUnique 130
diff --git a/compiler/GHC/HsToCore/Pmc.hs b/compiler/GHC/HsToCore/Pmc.hs
index 651f37f909..3292372e6e 100644
--- a/compiler/GHC/HsToCore/Pmc.hs
+++ b/compiler/GHC/HsToCore/Pmc.hs
@@ -238,10 +238,6 @@ instance Semigroup CIRB where
instance Monoid CIRB where
mempty = CIRB mempty mempty mempty mempty
-markAllRedundant :: CIRB -> CIRB
-markAllRedundant CIRB { cirb_cov = cov, cirb_inacc = inacc, cirb_red = red } =
- mempty { cirb_red = cov Semi.<> inacc Semi.<> red }
-
-- See Note [Determining inaccessible clauses]
ensureOneNotRedundant :: CIRB -> CIRB
ensureOneNotRedundant ci = case ci of
@@ -279,12 +275,14 @@ cirbsMatchGroup (PmMatchGroup matches) =
cirbsMatch :: PmMatch Post -> DsM CIRB
cirbsMatch PmMatch { pm_pats = red, pm_grhss = grhss } = do
- (is_covered, may_diverge, red_bangs) <- testRedSets red
+ (_is_covered, may_diverge, red_bangs) <- testRedSets red
+ -- Don't look at is_covered: If it is True, all children are redundant anyway,
+ -- unless there is a 'considerAccessible', which may break that rule
+ -- intentionally. See Note [considerAccessible] in "GHC.HsToCore.Pmc.Check".
cirb <- cirbsGRHSs grhss
pure $ addRedundantBangs red_bangs
-- See Note [Determining inaccessible clauses]
$ applyWhen may_diverge ensureOneNotRedundant
- $ applyWhen (not is_covered) markAllRedundant
$ cirb
cirbsGRHSs :: PmGRHSs Post -> DsM CIRB
diff --git a/compiler/GHC/HsToCore/Pmc/Check.hs b/compiler/GHC/HsToCore/Pmc/Check.hs
index 3ffd51fe7a..10d8574093 100644
--- a/compiler/GHC/HsToCore/Pmc/Check.hs
+++ b/compiler/GHC/HsToCore/Pmc/Check.hs
@@ -26,6 +26,7 @@ module GHC.HsToCore.Pmc.Check (
import GHC.Prelude
+import GHC.Builtin.Names ( hasKey, considerAccessibleIdKey, trueDataConKey )
import GHC.HsToCore.Monad ( DsM )
import GHC.HsToCore.Pmc.Types
import GHC.HsToCore.Pmc.Utils
@@ -124,6 +125,13 @@ checkGrd grd = CA $ \inc -> case grd of
pure CheckResult { cr_ret = RedSets { rs_cov = matched, rs_div = div, rs_bangs = bangs }
, cr_uncov = mempty
, cr_approx = Precise }
+ -- See point (3) of Note [considerAccessible]
+ PmCon x (PmAltConLike con) _ _ _
+ | x `hasKey` considerAccessibleIdKey
+ , con `hasKey` trueDataConKey
+ -> pure CheckResult { cr_ret = emptyRedSets { rs_cov = initNablas }
+ , cr_uncov = mempty
+ , cr_approx = Precise }
-- Con: Fall through on x ≁ K and refine with x ~ K ys and type info
PmCon x con tvs dicts args -> do
!div <- if isPmAltConMatchStrict con
@@ -269,4 +277,77 @@ Guards are an extreme example in this regard, with #11195 being a particularly
dreadful example: Since their RHS are often pretty much unique, we split on a
variable (the one representing the RHS) that doesn't occur anywhere else in the
program, so we don't actually get useful information out of that split!
+
+Note [considerAccessible]
+~~~~~~~~~~~~~~~~~~~~~~~~~
+Consider (T18610)
+
+ f :: Bool -> Int
+ f x = case (x, x) of
+ (True, True) -> 1
+ (False, False) -> 2
+ (True, False) -> 3 -- Warning: Redundant
+
+The third case is detected as redundant. But it may be the intent of the
+programmer to keep the dead code, in order for it not to bitrot or to support
+debugging scenarios. But there is no way to communicate that to the
+pattern-match checker! The only way is to deactivate pattern-match checking
+whole-sale, which is quite annoying. Hence, we define in "GHC.Exts":
+
+ considerAccessible = True
+
+'considerAccessible' is treated specially by the pattern-match checker in that a
+guard with it as the scrutinee expression will keep its parent clause alive:
+
+ g :: Bool -> Int
+ g x = case (x, x) of
+ (True, True) -> 1
+ (False, False) -> 2
+ (True, False) | GHC.Exts.considerAccessible -> 3 -- No warning
+
+The key bits of the implementation are:
+
+ 1. Its definition is recognised as known-key (see "GHC.Builtin.Names").
+ 2. After "GHC.HsToCore.Pmc.Desugar", the guard will end up as a 'PmCon', where
+ the match var is the known-key 'considerAccessible' and the constructor
+ against which it matches is 'True'.
+ 3. We recognise the 'PmCon' in 'GHC.HsToCore.Check.checkGrd' and inflate the
+ incoming set of values for all guards downstream to the unconstrained
+ 'initNablas' set, e.g. /all/ values.
+ (The set of values that falls through that particular guard is empty, as
+ matching 'considerAccessible' against 'True' can't fail.)
+
+Note that 'considerAccessible' breaks the invariant that incoming sets of values
+reaching syntactic children are subsets of that of the syntactic ancestor:
+A whole match, like that of the third clause of the example, might have no
+incoming value, but its single RHS has incoming values because of (3).
+
+That means the 'is_covered' flag computed in 'GHC.HsToCore.Pmc.cirbsMatch'
+is irrelevant and should not be used to flag all children as redundant (which is
+what we used to do).
+
+We achieve great benefits with a very simple implementation.
+There are caveats, though:
+
+ (A) Putting potentially failing guards /after/ the
+ 'considerAccessible' guard might lead to weird check results, e.g.,
+
+ h :: Bool -> Int
+ h x = case (x, x) of
+ (True, True) -> 1
+ (False, False) -> 2
+ (True, False) | GHC.Exts.considerAccessible, False <- x -> 3
+ -- Warning: Not matched: (_, _)
+
+ That *is* fixable, although we would pay with a much more complicated
+ implementation.
+ (B) If the programmer puts a 'considerAccessible' marker on an accessible
+ clause, the checker doesn't warn about it. E.g.,
+
+ f :: Bool -> Int
+ f True | considerAccessible = 0
+ f False = 1
+
+ will not emit any warning whatsoever. We could implement code that warns
+ here, but it wouldn't be as simple as it is now.
-}