module GHC.HsToCore.Pmc (
pmcPatBind, pmcMatches, pmcGRHSs,
isMatchContextPmChecked,
addTyCs, addCoreScrutTmCs, addHsScrutTmCs
) where
#include "HsVersions.h"
import GHC.Prelude
import GHC.HsToCore.Pmc.Types
import GHC.HsToCore.Pmc.Utils
import GHC.HsToCore.Pmc.Desugar
import GHC.HsToCore.Pmc.Check
import GHC.HsToCore.Pmc.Solver
import GHC.HsToCore.Pmc.Ppr
import GHC.Types.Basic (Origin(..))
import GHC.Core (CoreExpr)
import GHC.Driver.Session
import GHC.Driver.Env
import GHC.Hs
import GHC.Types.Id
import GHC.Types.SrcLoc
import GHC.Utils.Misc
import GHC.Utils.Outputable
import GHC.Utils.Panic
import GHC.Types.Var (EvVar)
import GHC.Tc.Types
import GHC.Tc.Utils.TcType (evVarPred)
import GHC.HsToCore.Expr (dsLExpr)
import GHC.HsToCore.Monad
import GHC.Data.Bag
import GHC.Data.IOEnv (updEnv, unsafeInterleaveM)
import GHC.Data.OrdList
import GHC.Utils.Monad (mapMaybeM)
import Control.Monad (when, forM_)
import qualified Data.Semigroup as Semi
import Data.List.NonEmpty ( NonEmpty(..) )
import qualified Data.List.NonEmpty as NE
import Data.Coerce
getLdiNablas :: DsM Nablas
getLdiNablas = do
nablas <- getPmNablas
isInhabited nablas >>= \case
True -> pure nablas
False -> pure initNablas
noCheckDs :: DsM a -> DsM a
noCheckDs k = do
dflags <- getDynFlags
let dflags' = foldl' wopt_unset dflags allPmCheckWarnings
updEnv (\env -> env{env_top = (env_top env) {hsc_dflags = dflags'} }) k
pmcPatBind :: DsMatchContext -> Id -> Pat GhcTc -> DsM ()
pmcPatBind ctxt@(DsMatchContext PatBindRhs loc) var p = do
!missing <- getLdiNablas
pat_bind <- noCheckDs $ desugarPatBind loc var p
tracePm "pmcPatBind {" (vcat [ppr ctxt, ppr var, ppr p, ppr pat_bind, ppr missing])
result <- unCA (checkPatBind pat_bind) missing
tracePm "}: " (ppr (cr_uncov result))
formatReportWarnings cirbsPatBind ctxt [var] result
pmcPatBind _ _ _ = pure ()
pmcGRHSs
:: HsMatchContext GhcRn
-> GRHSs GhcTc (LHsExpr GhcTc)
-> DsM (NonEmpty Nablas)
pmcGRHSs hs_ctxt guards@(GRHSs _ grhss _) = do
let combined_loc = foldl1 combineSrcSpans (map getLoc grhss)
ctxt = DsMatchContext hs_ctxt combined_loc
!missing <- getLdiNablas
matches <- noCheckDs $ desugarGRHSs combined_loc empty guards
tracePm "pmcGRHSs" (hang (vcat [ppr ctxt
, text "Guards:"])
2
(pprGRHSs hs_ctxt guards $$ ppr missing))
result <- unCA (checkGRHSs matches) missing
tracePm "}: " (ppr (cr_uncov result))
formatReportWarnings cirbsGRHSs ctxt [] result
return (ldiGRHSs (cr_ret result))
pmcMatches
:: DsMatchContext
-> [Id]
-> [LMatch GhcTc (LHsExpr GhcTc)]
-> DsM [(Nablas, NonEmpty Nablas)]
pmcMatches ctxt vars matches = do
!missing <- getLdiNablas
tracePm "pmcMatches {" $
hang (vcat [ppr ctxt, ppr vars, text "Matches:"])
2
(vcat (map ppr matches) $$ ppr missing)
case NE.nonEmpty matches of
Nothing -> do
let var = only vars
empty_case <- noCheckDs $ desugarEmptyCase var
result <- unCA (checkEmptyCase empty_case) missing
tracePm "}: " (ppr (cr_uncov result))
formatReportWarnings cirbsEmptyCase ctxt vars result
return []
Just matches -> do
matches <- noCheckDs $ desugarMatches vars matches
result <- unCA (checkMatchGroup matches) missing
tracePm "}: " (ppr (cr_uncov result))
formatReportWarnings cirbsMatchGroup ctxt vars result
return (NE.toList (ldiMatchGroup (cr_ret result)))
ldiMatchGroup :: PmMatchGroup Post -> NonEmpty (Nablas, NonEmpty Nablas)
ldiMatchGroup (PmMatchGroup matches) = ldiMatch <$> matches
ldiMatch :: PmMatch Post -> (Nablas, NonEmpty Nablas)
ldiMatch (PmMatch { pm_pats = red, pm_grhss = grhss }) =
(rs_cov red, ldiGRHSs grhss)
ldiGRHSs :: PmGRHSs Post -> NonEmpty Nablas
ldiGRHSs (PmGRHSs { pgs_grhss = grhss }) = ldiGRHS <$> grhss
ldiGRHS :: PmGRHS Post -> Nablas
ldiGRHS (PmGRHS { pg_grds = red }) = rs_cov red
data CIRB
= CIRB
{ cirb_cov :: !(OrdList SrcInfo)
, cirb_inacc :: !(OrdList SrcInfo)
, cirb_red :: !(OrdList SrcInfo)
, cirb_bangs :: !(OrdList SrcInfo)
}
instance Semigroup CIRB where
CIRB a b c d <> CIRB e f g h = CIRB (a <> e) (b <> f) (c <> g) (d <> h)
where (<>) = (Semi.<>)
instance Monoid CIRB where
mempty = CIRB mempty mempty mempty mempty
ensureOneNotRedundant :: CIRB -> CIRB
ensureOneNotRedundant ci = case ci of
CIRB { cirb_cov = NilOL, cirb_inacc = NilOL, cirb_red = ConsOL r rs }
-> ci { cirb_inacc = unitOL r, cirb_red = rs }
_ -> ci
addRedundantBangs :: OrdList SrcInfo -> CIRB -> CIRB
addRedundantBangs _red_bangs cirb@CIRB { cirb_cov = NilOL, cirb_inacc = NilOL } =
cirb
addRedundantBangs red_bangs cirb =
cirb { cirb_bangs = cirb_bangs cirb Semi.<> red_bangs }
testRedSets :: RedSets -> DsM (Bool, Bool, OrdList SrcInfo)
testRedSets RedSets { rs_cov = cov, rs_div = div, rs_bangs = bangs } = do
is_covered <- isInhabited cov
may_diverge <- isInhabited div
red_bangs <- flip mapMaybeM (fromOL bangs) $ \(nablas, bang) ->
isInhabited nablas >>= \case
True -> pure Nothing
False -> pure (Just bang)
pure (is_covered, may_diverge, toOL red_bangs)
cirbsMatchGroup :: PmMatchGroup Post -> DsM CIRB
cirbsMatchGroup (PmMatchGroup matches) =
Semi.sconcat <$> traverse cirbsMatch matches
cirbsMatch :: PmMatch Post -> DsM CIRB
cirbsMatch PmMatch { pm_pats = red, pm_grhss = grhss } = do
(_is_covered, may_diverge, red_bangs) <- testRedSets red
cirb <- cirbsGRHSs grhss
pure $ addRedundantBangs red_bangs
$ applyWhen may_diverge ensureOneNotRedundant
$ cirb
cirbsGRHSs :: PmGRHSs Post -> DsM CIRB
cirbsGRHSs (PmGRHSs { pgs_grhss = grhss }) = Semi.sconcat <$> traverse cirbsGRHS grhss
cirbsGRHS :: PmGRHS Post -> DsM CIRB
cirbsGRHS PmGRHS { pg_grds = red, pg_rhs = info } = do
(is_covered, may_diverge, red_bangs) <- testRedSets red
let cirb | is_covered = mempty { cirb_cov = unitOL info }
| may_diverge = mempty { cirb_inacc = unitOL info }
| otherwise = mempty { cirb_red = unitOL info }
pure (addRedundantBangs red_bangs cirb)
cirbsEmptyCase :: PmEmptyCase -> DsM CIRB
cirbsEmptyCase _ = pure mempty
cirbsPatBind :: PmPatBind Post -> DsM CIRB
cirbsPatBind = coerce cirbsGRHS
formatReportWarnings :: (ann -> DsM CIRB) -> DsMatchContext -> [Id] -> CheckResult ann -> DsM ()
formatReportWarnings collect ctx vars cr@CheckResult { cr_ret = ann } = do
cov_info <- collect ann
dflags <- getDynFlags
reportWarnings dflags ctx vars cr{cr_ret=cov_info}
reportWarnings :: DynFlags -> DsMatchContext -> [Id] -> CheckResult CIRB -> DsM ()
reportWarnings dflags ctx@(DsMatchContext kind loc) vars
CheckResult { cr_ret = CIRB { cirb_inacc = inaccessible_rhss
, cirb_red = redundant_rhss
, cirb_bangs = redundant_bangs }
, cr_uncov = uncovered
, cr_approx = precision }
= when (flag_i || flag_u || flag_b) $ do
unc_examples <- getNFirstUncovered vars (maxPatterns + 1) uncovered
let exists_r = flag_i && notNull redundant_rhss
exists_i = flag_i && notNull inaccessible_rhss
exists_u = flag_u && notNull unc_examples
exists_b = flag_b && notNull redundant_bangs
approx = precision == Approximate
when (approx && (exists_u || exists_i)) $
putSrcSpanDs loc (warnDs NoReason approx_msg)
when exists_b $ forM_ redundant_bangs $ \(SrcInfo (L l q)) ->
putSrcSpanDs l (warnDs (Reason Opt_WarnRedundantBangPatterns)
(pprEqn q "has redundant bang"))
when exists_r $ forM_ redundant_rhss $ \(SrcInfo (L l q)) ->
putSrcSpanDs l (warnDs (Reason Opt_WarnOverlappingPatterns)
(pprEqn q "is redundant"))
when exists_i $ forM_ inaccessible_rhss $ \(SrcInfo (L l q)) ->
putSrcSpanDs l (warnDs (Reason Opt_WarnOverlappingPatterns)
(pprEqn q "has inaccessible right hand side"))
when exists_u $ putSrcSpanDs loc $ warnDs flag_u_reason $
pprEqns vars unc_examples
where
flag_i = overlapping dflags kind
flag_u = exhaustive dflags kind
flag_b = redundantBang dflags
flag_u_reason = maybe NoReason Reason (exhaustiveWarningFlag kind)
maxPatterns = maxUncoveredPatterns dflags
pprEqn q txt = pprContext True ctx (text txt) $ \f ->
f (q <+> matchSeparator kind <+> text "...")
pprEqns vars nablas = pprContext False ctx (text "are non-exhaustive") $ \_ ->
case vars of
[] -> text "Guards do not cover entire pattern space"
_ -> let us = map (\nabla -> pprUncovered nabla vars) nablas
pp_tys = pprQuotedList $ map idType vars
in hang
(text "Patterns of type" <+> pp_tys <+> text "not matched:")
4
(vcat (take maxPatterns us) $$ dots maxPatterns us)
approx_msg = vcat
[ hang
(text "Pattern match checker ran into -fmax-pmcheck-models="
<> int (maxPmCheckModels dflags)
<> text " limit, so")
2
( bullet <+> text "Redundant clauses might not be reported at all"
$$ bullet <+> text "Redundant clauses might be reported as inaccessible"
$$ bullet <+> text "Patterns reported as unmatched might actually be matched")
, text "Increase the limit or resolve the warnings to suppress this message." ]
getNFirstUncovered :: [Id] -> Int -> Nablas -> DsM [Nabla]
getNFirstUncovered vars n (MkNablas nablas) = go n (bagToList nablas)
where
go 0 _ = pure []
go _ [] = pure []
go n (nabla:nablas) = do
front <- generateInhabitingPatterns vars n nabla
back <- go (n length front) nablas
pure (front ++ back)
dots :: Int -> [a] -> SDoc
dots maxPatterns qs
| qs `lengthExceeds` maxPatterns = text "..."
| otherwise = empty
pprContext :: Bool -> DsMatchContext -> SDoc -> ((SDoc -> SDoc) -> SDoc) -> SDoc
pprContext singular (DsMatchContext kind _loc) msg rest_of_msg_fun
= vcat [text txt <+> msg,
sep [ text "In" <+> ppr_match <> char ':'
, nest 4 (rest_of_msg_fun pref)]]
where
txt | singular = "Pattern match"
| otherwise = "Pattern match(es)"
(ppr_match, pref)
= case kind of
FunRhs { mc_fun = L _ fun }
-> (pprMatchContext kind, \ pp -> ppr fun <+> pp)
_ -> (pprMatchContext kind, \ pp -> pp)
locallyExtendPmNablas :: (Nablas -> DsM Nablas) -> DsM a -> DsM a
locallyExtendPmNablas ext k = do
nablas <- getLdiNablas
nablas' <- unsafeInterleaveM $ ext nablas
updPmNablas nablas' k
addTyCs :: Origin -> Bag EvVar -> DsM a -> DsM a
addTyCs origin ev_vars m = do
dflags <- getDynFlags
applyWhen (needToRunPmCheck dflags origin)
(locallyExtendPmNablas $ \nablas ->
addPhiCtsNablas nablas (PhiTyCt . evVarPred <$> ev_vars))
m
addCoreScrutTmCs :: Maybe CoreExpr -> [Id] -> DsM a -> DsM a
addCoreScrutTmCs Nothing _ k = k
addCoreScrutTmCs (Just scr) [x] k =
flip locallyExtendPmNablas k $ \nablas ->
addPhiCtsNablas nablas (unitBag (PhiCoreCt x scr))
addCoreScrutTmCs _ _ _ = panic "addCoreScrutTmCs: scrutinee, but more than one match id"
addHsScrutTmCs :: Maybe (LHsExpr GhcTc) -> [Id] -> DsM a -> DsM a
addHsScrutTmCs Nothing _ k = k
addHsScrutTmCs (Just scr) vars k = do
scr_e <- dsLExpr scr
addCoreScrutTmCs (Just scr_e) vars k