diff --git a/compiler/GHC/Core/ConLike.hs b/compiler/GHC/Core/ConLike.hs index e609bd4b51b3021ea3037ab6f8c52c1d479f43fb..683e03d187356883cd52deef96cb1a5127941edd 100644 --- a/compiler/GHC/Core/ConLike.hs +++ b/compiler/GHC/Core/ConLike.hs @@ -47,6 +47,7 @@ import GHC.Utils.Outputable import Data.Maybe( isJust ) import qualified Data.Data as Data +import qualified Data.List as List {- ************************************************************************ @@ -224,8 +225,10 @@ conLikeFieldType (RealDataCon dc) label = dataConFieldType dc label -- | The ConLikes that have *all* the given fields -conLikesWithFields :: [ConLike] -> [FieldLabelString] -> [ConLike] -conLikesWithFields con_likes lbls = filter has_flds con_likes +conLikesWithFields :: [ConLike] -> [FieldLabelString] + -> ( [ConLike] -- ConLikes containing the fields + , [ConLike] ) -- ConLikes not containing the fields +conLikesWithFields con_likes lbls = List.partition has_flds con_likes where has_flds dc = all (has_fld dc) lbls has_fld dc lbl = any (\ fl -> flLabel fl == lbl) (conLikeFieldLabels dc) diff --git a/compiler/GHC/Driver/Flags.hs b/compiler/GHC/Driver/Flags.hs index 736505c7f10ab54c6f684d9e8a1b6a1549eb552f..005a324017f2fe4caffd18a38d6edc8dae0db017 100644 --- a/compiler/GHC/Driver/Flags.hs +++ b/compiler/GHC/Driver/Flags.hs @@ -684,6 +684,7 @@ data WarningFlag = | Opt_WarnMissingRoleAnnotations -- Since 9.8 | Opt_WarnImplicitRhsQuantification -- Since 9.8 | Opt_WarnIncompleteExportWarnings -- Since 9.8 + | Opt_WarnIncompleteRecordSelectors -- Since 9.10 deriving (Eq, Ord, Show, Enum) -- | Return the names of a WarningFlag @@ -794,6 +795,7 @@ warnFlagNames wflag = case wflag of Opt_WarnMissingRoleAnnotations -> "missing-role-annotations" :| [] Opt_WarnImplicitRhsQuantification -> "implicit-rhs-quantification" :| [] Opt_WarnIncompleteExportWarnings -> "incomplete-export-warnings" :| [] + Opt_WarnIncompleteRecordSelectors -> "incomplete-record-selectors" :| [] -- ----------------------------------------------------------------------------- -- Standard sets of warning options diff --git a/compiler/GHC/Driver/Session.hs b/compiler/GHC/Driver/Session.hs index 60ba6526bbb23be35d38860ed487bbb05fdf417a..49ab225cf0e06e424e66a2e95bca818f9aa3962b 100644 --- a/compiler/GHC/Driver/Session.hs +++ b/compiler/GHC/Driver/Session.hs @@ -2263,7 +2263,8 @@ wWarningFlagsDeps = mconcat [ warnSpec Opt_WarnTermVariableCapture, warnSpec Opt_WarnMissingRoleAnnotations, warnSpec Opt_WarnImplicitRhsQuantification, - warnSpec Opt_WarnIncompleteExportWarnings + warnSpec Opt_WarnIncompleteExportWarnings, + warnSpec Opt_WarnIncompleteRecordSelectors ] warningGroupsDeps :: [(Deprecation, FlagSpec WarningGroup)] diff --git a/compiler/GHC/HsToCore/Errors/Ppr.hs b/compiler/GHC/HsToCore/Errors/Ppr.hs index cc7222f447c5566b9eb9264cf4d72dca3ee355e6..402b5a2f49e3d7ff7c4e8fa42e17333af2ff608b 100644 --- a/compiler/GHC/HsToCore/Errors/Ppr.hs +++ b/compiler/GHC/HsToCore/Errors/Ppr.hs @@ -207,6 +207,10 @@ instance Diagnostic DsMessage where <+> text "for"<+> quotes (ppr lhs_id) <+> text "might fire first") ] + DsIncompleteRecordSelector name cons_wo_field not_full_examples -> mkSimpleDecorated $ + text "The application of the record field" <+> quotes (ppr name) + <+> text "may fail for the following constructors:" + <+> vcat (map ppr cons_wo_field ++ [text "..." | not_full_examples]) diagnosticReason = \case DsUnknownMessage m -> diagnosticReason m @@ -237,6 +241,7 @@ instance Diagnostic DsMessage where DsRecBindsNotAllowedForUnliftedTys{} -> ErrorWithoutFlag DsRuleMightInlineFirst{} -> WarningWithFlag Opt_WarnInlineRuleShadowing DsAnotherRuleMightFireFirst{} -> WarningWithFlag Opt_WarnInlineRuleShadowing + DsIncompleteRecordSelector{} -> WarningWithFlag Opt_WarnIncompleteRecordSelectors diagnosticHints = \case DsUnknownMessage m -> diagnosticHints m @@ -273,6 +278,7 @@ instance Diagnostic DsMessage where DsRecBindsNotAllowedForUnliftedTys{} -> noHints DsRuleMightInlineFirst _ lhs_id rule_act -> [SuggestAddInlineOrNoInlinePragma lhs_id rule_act] DsAnotherRuleMightFireFirst _ bad_rule _ -> [SuggestAddPhaseToCompetingRule bad_rule] + DsIncompleteRecordSelector{} -> noHints diagnosticCode = constructorCode diff --git a/compiler/GHC/HsToCore/Errors/Types.hs b/compiler/GHC/HsToCore/Errors/Types.hs index 608bfa28544780b186d2f1d1b6fbca1f40b7ba8a..a5b85218fad5f5362860dea6ac7ac1c4e3ef5520 100644 --- a/compiler/GHC/HsToCore/Errors/Types.hs +++ b/compiler/GHC/HsToCore/Errors/Types.hs @@ -8,6 +8,7 @@ import GHC.Prelude import GHC.Core (CoreRule, CoreExpr, RuleName) import GHC.Core.DataCon +import GHC.Core.ConLike import GHC.Core.Type import GHC.Driver.DynFlags (DynFlags, xopt) import GHC.Driver.Flags (WarningFlag) @@ -147,6 +148,23 @@ data DsMessage !RuleName -- the \"bad\" rule !Var + {-| DsIncompleteRecordSelector is a warning triggered when we are not certain whether + a record selector application will be successful. Currently, this means that + the warning is triggered when there is a record selector of a data type that + does not have that field in all its constructors. + + Example(s): + data T = T1 | T2 {x :: Bool} + f :: T -> Bool + f a = x a + + Test cases: + DsIncompleteRecSel1 + DsIncompleteRecSel2 + DsIncompleteRecSel3 + -} + | DsIncompleteRecordSelector !Name ![ConLike] !Bool + deriving Generic -- The positional number of the argument for an expression (first, second, third, etc) diff --git a/compiler/GHC/HsToCore/Expr.hs b/compiler/GHC/HsToCore/Expr.hs index 59970138dd1b27d194350cb5efaf384ec8878257..8b0283f6e5bd87101befba4c52ac15bc0c72e5da 100644 --- a/compiler/GHC/HsToCore/Expr.hs +++ b/compiler/GHC/HsToCore/Expr.hs @@ -3,6 +3,7 @@ {-# LANGUAGE ViewPatterns #-} {-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} +{-# LANGUAGE LambdaCase #-} {- (c) The University of Glasgow 2006 @@ -31,7 +32,7 @@ import GHC.HsToCore.Monad import GHC.HsToCore.Pmc import GHC.HsToCore.Errors.Types import GHC.Types.SourceText -import GHC.Types.Name +import GHC.Types.Name hiding (varName) import GHC.Core.FamInstEnv( topNormaliseType ) import GHC.HsToCore.Quote import GHC.HsToCore.Ticks (stripTicksTopHsExpr) @@ -51,6 +52,7 @@ import GHC.Core.Make import GHC.Driver.Session import GHC.Types.CostCentre import GHC.Types.Id +import GHC.Types.Id.Info import GHC.Types.Id.Make import GHC.Unit.Module import GHC.Core.ConLike @@ -67,6 +69,7 @@ import GHC.Utils.Panic import GHC.Utils.Panic.Plain import GHC.Core.PatSyn import Control.Monad +import GHC.Types.Error {- ************************************************************************ @@ -230,7 +233,38 @@ dsLExpr (L loc e) = putSrcSpanDsA loc $ dsExpr e -- | Desugar a typechecked expression. dsExpr :: HsExpr GhcTc -> DsM CoreExpr dsExpr (HsVar _ (L _ id)) = dsHsVar id -dsExpr (HsRecSel _ (FieldOcc id _)) = dsHsVar id + +{- Record selectors are warned about if they are not +present in all of the parent data type's constructor, +or always in case of pattern synonym record selectors +(regulated by a flag). However, this only produces +a warning if it's not a part of a record selector +application. For example: + + data T = T1 | T2 {s :: Bool} + f x = s x -- the warning from this case will be supressed + +See the `HsApp` case for where it is filtered out +-} +dsExpr (HsRecSel _ (FieldOcc id _)) + = do { let name = getName id + RecSelId {sel_cons = (_, cons_wo_field)} + = idDetails id + ; cons_trimmed <- trim_cons cons_wo_field + ; unless (null cons_wo_field) $ diagnosticDs + $ DsIncompleteRecordSelector name cons_trimmed (cons_trimmed /= cons_wo_field) + -- This only produces a warning if it's not a part of a + -- record selector application (e.g. `s a` where `s` is a selector) + -- See the `HsApp` case for where it is filtered out + ; dsHsVar id } + where + trim_cons :: [ConLike] -> DsM [ConLike] + trim_cons cons_wo_field = do + dflags <- getDynFlags + let maxConstructors = maxUncoveredPatterns dflags + return $ take maxConstructors cons_wo_field + + dsExpr (HsUnboundVar (HER ref _ _) _) = dsEvTerm =<< readMutVar ref -- See Note [Holes] in GHC.Tc.Types.Constraint @@ -297,9 +331,27 @@ dsExpr (HsLamCase _ lc_variant matches) = uncurry mkCoreLams <$> matchWrapper (LamCaseAlt lc_variant) Nothing matches dsExpr e@(HsApp _ fun arg) - = do { fun' <- dsLExpr fun + -- We want to have a special case that uses the PMC information to filter + -- out some of the incomplete record selectors warnings and not trigger + -- the warning emitted during the desugaring of dsExpr(HsRecSel) + -- See Note [Detecting incomplete record selectors] in GHC.HsToCore.Pmc + = do { (msgs, fun') <- captureMessagesDs $ dsLExpr fun + -- Make sure to filter out the generic incomplete record selector warning + -- if it's a raw record selector ; arg' <- dsLExpr arg + ; case getIdFromTrivialExpr_maybe fun' of + Just fun_id | isRecordSelector fun_id + -> do { let msgs' = filterMessages is_incomplete_rec_sel_msg msgs + ; addMessagesDs msgs' + ; pmcRecSel fun_id arg' } + _ -> addMessagesDs msgs ; return $ mkCoreAppDs (text "HsApp" <+> ppr e) fun' arg' } + where + is_incomplete_rec_sel_msg :: MsgEnvelope DsMessage -> Bool + is_incomplete_rec_sel_msg (MsgEnvelope {errMsgDiagnostic = DsIncompleteRecordSelector{}}) + = False + is_incomplete_rec_sel_msg _ = True + dsExpr e@(HsAppType {}) = dsHsWrapped e diff --git a/compiler/GHC/HsToCore/Monad.hs b/compiler/GHC/HsToCore/Monad.hs index 6ba19bb95892004e6cd7a2363cc44f3f9019ea46..1a6533103b0429449a73fddf32e7d9ef7a541613 100644 --- a/compiler/GHC/HsToCore/Monad.hs +++ b/compiler/GHC/HsToCore/Monad.hs @@ -45,6 +45,7 @@ module GHC.HsToCore.Monad ( -- Warnings and errors DsWarning, diagnosticDs, errDsCoreExpr, failWithDs, failDs, discardWarningsDs, + addMessagesDs, captureMessagesDs, -- Data types DsMatchContext(..), @@ -443,6 +444,12 @@ diagnosticDs dsMessage ; let msg = mkMsgEnvelope diag_opts loc (ds_name_ppr_ctx env) dsMessage ; updMutVar (ds_msgs env) (\ msgs -> msg `addMessage` msgs) } +addMessagesDs :: Messages DsMessage -> DsM () +addMessagesDs msgs1 + = do { msg_var <- ds_msgs <$> getGblEnv + ; msgs0 <- liftIO $ readIORef msg_var + ; liftIO $ writeIORef msg_var (msgs0 `unionMessages` msgs1) } + -- | Issue an error, but return the expression for (), so that we can continue -- reporting errors. errDsCoreExpr :: DsMessage -> DsM CoreExpr @@ -458,6 +465,13 @@ failWithDs msg failDs :: DsM a failDs = failM +captureMessagesDs :: DsM a -> DsM (Messages DsMessage, a) +captureMessagesDs thing_inside + = do { msg_var <- liftIO $ newIORef emptyMessages + ; res <- updGblEnv (\gbl -> gbl {ds_msgs = msg_var}) thing_inside + ; msgs <- liftIO $ readIORef msg_var + ; return (msgs, res) } + mkNamePprCtxDs :: DsM NamePprCtx mkNamePprCtxDs = ds_name_ppr_ctx <$> getGblEnv diff --git a/compiler/GHC/HsToCore/Pmc.hs b/compiler/GHC/HsToCore/Pmc.hs index 9fde238fe97178f724e4065ab1d1149f8deaa9f1..ca49e530a77bf048b8b1f4df5e13e51324b2a92c 100644 --- a/compiler/GHC/HsToCore/Pmc.hs +++ b/compiler/GHC/HsToCore/Pmc.hs @@ -35,11 +35,12 @@ -- 'ldiMatch'. See Section 4.1 of the paper. module GHC.HsToCore.Pmc ( -- Checking and printing - pmcPatBind, pmcMatches, pmcGRHSs, + pmcPatBind, pmcMatches, pmcGRHSs, pmcRecSel, isMatchContextPmChecked, isMatchContextPmChecked_SinglePat, -- See Note [Long-distance information] - addTyCs, addCoreScrutTmCs, addHsScrutTmCs, getLdiNablas + addTyCs, addCoreScrutTmCs, addHsScrutTmCs, getLdiNablas, + getNFirstUncovered ) where import GHC.Prelude @@ -51,7 +52,7 @@ import GHC.HsToCore.Pmc.Desugar import GHC.HsToCore.Pmc.Check import GHC.HsToCore.Pmc.Solver import GHC.Types.Basic (Origin(..)) -import GHC.Core (CoreExpr) +import GHC.Core import GHC.Driver.DynFlags import GHC.Hs import GHC.Types.Id @@ -59,21 +60,20 @@ import GHC.Types.SrcLoc import GHC.Utils.Misc import GHC.Utils.Outputable import GHC.Utils.Panic -import GHC.Types.Var (EvVar) +import GHC.Types.Var (EvVar, Var (..)) +import GHC.Types.Id.Info import GHC.Tc.Utils.TcType (evVarPred) -import GHC.Tc.Utils.Monad (updTopFlags) import {-# SOURCE #-} GHC.HsToCore.Expr (dsLExpr) import GHC.HsToCore.Monad import GHC.Data.Bag -import GHC.Data.IOEnv (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 +import GHC.Tc.Utils.Monad -- -- * Exported entry points to the checker @@ -193,9 +193,92 @@ pmcMatches ctxt vars matches = {-# SCC "pmcMatches" #-} do {-# SCC "formatReportWarnings" #-} formatReportWarnings ReportMatchGroup ctxt vars result return (NE.toList (ldiMatchGroup (cr_ret result))) +{- +Note [Detecting incomplete record selectors] +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +A record selector occurence is incomplete iff. it could fail due to +being applied to a data type constructor not present for this record field. + +e.g. + data T = T1 | T2 {x :: Int} + d = x someComputation -- `d` may fail + +There are 4 parts to detecting and warning about +incomplete record selectors to consider: + + - Computing which constructors a general application of a record field will succeed on, + and which ones it will fail on. This is stored in the `sel_cons` field of + `IdDetails` datatype, which is a part of an `Id` and calculated when renaming a + record selector in `mkOneRecordSelector` + + - Emitting a warning whenever a `HasField` constraint is solved. + This is checked in `matchHasField` and emitted only for when + the constraint is resolved with an implicit instance rather than a + custom one (since otherwise the warning will be emitted in + the custom implementation anyways) + + e.g. + g :: HasField "x" t Int => t -> Int + g = getField @"x" + + f :: T -> Int + f = g -- warning will be emitted here + + - Emitting a warning for a general occurence of the record selector + This is done during the renaming of a `HsRecSel` expression in `dsExpr` + and simply pulls the information about incompleteness from the `Id` + + e.g. + l :: T -> Int + l a = x a -- warning will be emitted here + + - Emitting a warning for a record selector `sel` applied to a variable `y`. + In that case we want to use the long-distance information from the + pattern match checker to rule out impossible constructors + (See Note [Long-distance information]). We first add constraints to + the long-distance `Nablas` that `y` cannot be one of the constructors that + contain `sel` (function `checkRecSel` in GHC.HsToCore.Pmc.Check). If the + `Nablas` are still inhabited, we emit a warning with the inhabiting constructors + as examples of where `sel` may fail. + + e.g. + z :: T -> Int + z T1 = 0 + z a = x a -- warning will not be emitted here since `a` can only be `T2` +-} + +pmcRecSel :: Id -- ^ Id of the selector + -> CoreExpr -- ^ Core expression of the argument to the selector + -> DsM () +pmcRecSel sel_id arg + | RecSelId{ sel_cons = (cons_w_field, _ : _) } <- idDetails sel_id = do + !missing <- getLdiNablas + + tracePm "pmcRecSel {" (ppr sel_id) + CheckResult{ cr_ret = PmRecSel{ pr_arg_var = arg_id }, cr_uncov = uncov_nablas } + <- unCA (checkRecSel (PmRecSel () arg cons_w_field)) missing + tracePm "}: " $ ppr uncov_nablas + + inhabited <- isInhabited uncov_nablas + when inhabited $ warn_incomplete arg_id uncov_nablas + where + sel_name = varName sel_id + warn_incomplete arg_id uncov_nablas = do + dflags <- getDynFlags + let maxConstructors = maxUncoveredPatterns dflags + unc_examples <- getNFirstUncovered MinimalCover [arg_id] (maxConstructors + 1) uncov_nablas + let cons = [con | unc_example <- unc_examples + , Just (PACA (PmAltConLike con) _ _) <- [lookupSolution unc_example arg_id]] + not_full_examples = length cons == (maxConstructors + 1) + cons' = take maxConstructors cons + diagnosticDs $ DsIncompleteRecordSelector sel_name cons' not_full_examples + +pmcRecSel _ _ = return () + {- Note [pmcPatBind doesn't warn on pattern guards] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @pmcPatBind@'s main purpose is to check vanilla pattern bindings, like +>>>>>>> 8760510af3 (This MR is an implementation of the proposal #516.) @x :: Int; Just x = e@, which is in a @PatBindRhs@ context. But its caller is also called for individual pattern guards in a @StmtCtxt@. For example, both pattern guards in @f x y | True <- x, False <- y = ...@ will diff --git a/compiler/GHC/HsToCore/Pmc/Check.hs b/compiler/GHC/HsToCore/Pmc/Check.hs index 0adc29406e18d8be2a1ea2681b42d5bd412e477f..09108c0a64e2b65e3ca23f651de012361c401f0d 100644 --- a/compiler/GHC/HsToCore/Pmc/Check.hs +++ b/compiler/GHC/HsToCore/Pmc/Check.hs @@ -19,7 +19,7 @@ -- "GHC.HsToCore.Pmc.Solver". module GHC.HsToCore.Pmc.Check ( CheckAction(..), - checkMatchGroup, checkGRHSs, checkPatBind, checkEmptyCase + checkMatchGroup, checkGRHSs, checkPatBind, checkEmptyCase, checkRecSel ) where import GHC.Prelude @@ -33,11 +33,15 @@ import GHC.Driver.DynFlags import GHC.Utils.Outputable import GHC.Tc.Utils.TcType (evVarPred) import GHC.Data.OrdList +import GHC.Data.Bag import qualified Data.Semigroup as Semi import Data.List.NonEmpty ( NonEmpty(..) ) import qualified Data.List.NonEmpty as NE import Data.Coerce +import GHC.Types.Var +import GHC.Core +import GHC.Core.Utils -- | Coverage checking action. Can be composed 'leftToRight' or 'topToBottom'. newtype CheckAction a = CA { unCA :: Nablas -> DsM (CheckResult a) } @@ -185,6 +189,20 @@ checkEmptyCase pe@(PmEmptyCase { pe_var = var }) = CA $ \inc -> do checkPatBind :: (PmPatBind Pre) -> CheckAction (PmPatBind Post) checkPatBind = coerce checkGRHS +checkRecSel :: PmRecSel () -> CheckAction (PmRecSel Id) +-- See Note [Detecting incomplete record selectors] in GHC.HsToCore.Pmc +checkRecSel pr@(PmRecSel { pr_arg = arg, pr_cons = cons }) = CA $ \inc -> do + arg_id <- case arg of + Var arg_id -> return arg_id + _ -> mkPmId $ exprType arg + + let con_cts = map (PhiNotConCt arg_id . PmAltConLike) cons + arg_ct = PhiCoreCt arg_id arg + phi_cts = listToBag (arg_ct : con_cts) + unc <- addPhiCtsNablas inc phi_cts + pure CheckResult { cr_ret = pr{ pr_arg_var = arg_id }, cr_uncov = unc, cr_approx = mempty } + + {- Note [Checking EmptyCase] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -XEmptyCase is useful for matching on empty data types like 'Void'. For example, diff --git a/compiler/GHC/HsToCore/Pmc/Types.hs b/compiler/GHC/HsToCore/Pmc/Types.hs index d3046e58124bb3d7c959010c6073472764547979..46c159c9518ed829790363e81cb63f8afa919447 100644 --- a/compiler/GHC/HsToCore/Pmc/Types.hs +++ b/compiler/GHC/HsToCore/Pmc/Types.hs @@ -21,7 +21,8 @@ module GHC.HsToCore.Pmc.Types ( SrcInfo(..), PmGrd(..), GrdVec(..), -- ** Guard tree language - PmMatchGroup(..), PmMatch(..), PmGRHSs(..), PmGRHS(..), PmPatBind(..), PmEmptyCase(..), + PmMatchGroup(..), PmMatch(..), PmGRHSs(..), PmGRHS(..), + PmPatBind(..), PmEmptyCase(..), PmRecSel(..), -- * Coverage Checking types RedSets (..), Precision (..), CheckResult (..), @@ -43,6 +44,7 @@ import GHC.Types.Id import GHC.Types.Var (EvVar) import GHC.Types.SrcLoc import GHC.Utils.Outputable +import GHC.Core.ConLike import GHC.Core.Type import GHC.Core @@ -130,6 +132,8 @@ newtype PmPatBind p = -- rather than on the pattern bindings. PmPatBind (PmGRHS p) +-- A guard tree denoting a record selector application +data PmRecSel v = PmRecSel { pr_arg_var :: v, pr_arg :: CoreExpr, pr_cons :: [ConLike] } instance Outputable SrcInfo where ppr (SrcInfo (L (RealSrcSpan rss _) _)) = ppr (srcSpanStartLine rss) ppr (SrcInfo (L s _)) = ppr s diff --git a/compiler/GHC/HsToCore/Pmc/Utils.hs b/compiler/GHC/HsToCore/Pmc/Utils.hs index b208723e7b4542cec3511b8d4810f0b6dfeeb7b9..12423142f68b80f2f4aa5addb43f96fa77c4f739 100644 --- a/compiler/GHC/HsToCore/Pmc/Utils.hs +++ b/compiler/GHC/HsToCore/Pmc/Utils.hs @@ -61,6 +61,7 @@ allPmCheckWarnings = , Opt_WarnIncompleteUniPatterns , Opt_WarnIncompletePatternsRecUpd , Opt_WarnOverlappingPatterns + , Opt_WarnIncompleteRecordSelectors ] -- | Check whether the redundancy checker should run (redundancy only) diff --git a/compiler/GHC/IfaceToCore.hs b/compiler/GHC/IfaceToCore.hs index 1d6c49b6b2a18ee13228ba211bfb9736718fc39a..8924cc90fc587380e5c2b11d116c857b8eba6207 100644 --- a/compiler/GHC/IfaceToCore.hs +++ b/compiler/GHC/IfaceToCore.hs @@ -1688,7 +1688,16 @@ tcIdDetails nm _ (IfRecSelId tc _first_con naughty fl) = do { tc' <- either (fmap RecSelData . tcIfaceTyCon) (fmap (RecSelPatSyn . tyThingPatSyn) . tcIfaceDecl False) tc - ; return (RecSelId { sel_tycon = tc', sel_naughty = naughty, sel_fieldLabel = fl { flSelector = nm } }) } + ; let all_cons = recSelParentCons tc' + cons_partitioned + = conLikesWithFields all_cons [flLabel fl] + ; return (RecSelId + { sel_tycon = tc' + , sel_naughty = naughty + , sel_fieldLabel = fl { flSelector = nm } + , sel_cons = cons_partitioned } + -- Reconstructed here since we don't want Uniques in the Iface file + ) } where tyThingPatSyn (AConLike (PatSynCon ps)) = ps tyThingPatSyn _ = panic "tcIdDetails: expecting patsyn" diff --git a/compiler/GHC/Tc/Errors/Ppr.hs b/compiler/GHC/Tc/Errors/Ppr.hs index a698af9dff4b8ea8bf202e3f4fcba2d9bac5a982..72080273b2becf34630fa45f4c28f80417b1c348 100644 --- a/compiler/GHC/Tc/Errors/Ppr.hs +++ b/compiler/GHC/Tc/Errors/Ppr.hs @@ -1479,6 +1479,9 @@ instance Diagnostic TcRnMessage where TcRnPartialFieldSelector fld -> mkSimpleDecorated $ sep [text "Use of partial record field selector" <> colon, nest 2 $ quotes (ppr (occName fld))] + TcRnHasFieldResolvedIncomplete name -> mkSimpleDecorated $ + text "The invocation of `getField` on the record field" <+> quotes (ppr name) + <+> text "may produce an error since it is not defined for all data constructors" TcRnBadFieldAnnotation n con reason -> mkSimpleDecorated $ hang (pprBadFieldAnnotationReason reason) 2 (text "on the" <+> speakNth n @@ -2291,6 +2294,8 @@ instance Diagnostic TcRnMessage where -> ErrorWithoutFlag TcRnPartialFieldSelector{} -> WarningWithFlag Opt_WarnPartialFields + TcRnHasFieldResolvedIncomplete{} + -> WarningWithFlag Opt_WarnIncompleteRecordSelectors TcRnBadFieldAnnotation _ _ LazyFieldsDisabled -> ErrorWithoutFlag TcRnBadFieldAnnotation{} @@ -2928,6 +2933,8 @@ instance Diagnostic TcRnMessage where -> noHints TcRnPartialFieldSelector{} -> noHints + TcRnHasFieldResolvedIncomplete{} + -> noHints TcRnBadFieldAnnotation _ _ LazyFieldsDisabled -> [suggestExtension LangExt.StrictData] TcRnBadFieldAnnotation{} diff --git a/compiler/GHC/Tc/Errors/Types.hs b/compiler/GHC/Tc/Errors/Types.hs index 2d9e197d69f631df0bca8e2c51f680d76b361558..044070578f0b71f38c3e638426e98d157e3a89d5 100644 --- a/compiler/GHC/Tc/Errors/Types.hs +++ b/compiler/GHC/Tc/Errors/Types.hs @@ -3416,6 +3416,24 @@ data TcRnMessage where TcRnPartialFieldSelector :: !FieldLabel -- ^ The selector -> TcRnMessage + {-| TcRnHasFieldResolvedIncomplete is a warning triggered when a HasField constraint + is resolved for a record field for which a `getField @"field"` application + might not be successful. Currently, this means that the warning is triggered when + the parent data type of that record field does not have that field in all + its constructors. + + Example(s): + data T = T1 | T2 {x :: Bool} + f :: HasField t "x" Bool => t -> Bool + f = getField @"x" + g :: T -> Bool + g = f + + Test cases: + TcIncompleteRecSel + -} + TcRnHasFieldResolvedIncomplete :: !Name -> TcRnMessage + {-| TcRnBadFieldAnnotation is an error/warning group indicating that a strictness/unpack related data type field annotation is invalid. -} diff --git a/compiler/GHC/Tc/Instance/Class.hs b/compiler/GHC/Tc/Instance/Class.hs index a22ff699d16ca245e9127488aa929f0eda2f2a16..bb2182817a9d1ad645e6de934370636f7595d25b 100644 --- a/compiler/GHC/Tc/Instance/Class.hs +++ b/compiler/GHC/Tc/Instance/Class.hs @@ -54,6 +54,9 @@ import GHC.Utils.Misc( splitAtList, fstOf3 ) import GHC.Data.FastString import Language.Haskell.Syntax.Basic (FieldLabelString(..)) +import GHC.Types.Id.Info +import GHC.Tc.Errors.Types +import Control.Monad {- ******************************************************************* * * @@ -913,7 +916,8 @@ matchHasField dflags short_cut clas tys -- the field selector should be in scope , Just gre <- lookupGRE_FieldLabel rdr_env fl - -> do { sel_id <- tcLookupId (flSelector fl) + -> do { let name = flSelector fl + ; sel_id <- tcLookupId name ; (tv_prs, preds, sel_ty) <- tcInstType newMetaTyVars sel_id -- The first new wanted constraint equates the actual @@ -943,7 +947,11 @@ matchHasField dflags short_cut clas tys ; if not (isNaughtyRecordSelector sel_id) && isTauTy sel_ty then do { -- See Note [Unused name reporting and HasField] addUsedGRE AllDeprecationWarnings gre - ; keepAlive (greName gre) + ; keepAlive name + ; unless (null $ snd $ sel_cons $ idDetails sel_id) + $ addDiagnostic $ TcRnHasFieldResolvedIncomplete name + -- Only emit an incomplete selector warning if it's an implicit instance + -- See Note [Detecting incomplete record selectors] in GHC.HsToCore.Pmc ; return OneInst { cir_new_theta = theta , cir_mk_ev = mk_ev , cir_coherence = IsCoherent diff --git a/compiler/GHC/Tc/Solver/Dict.hs b/compiler/GHC/Tc/Solver/Dict.hs index 8c85d9e06df5da0adc610d5d558a076414f5ebc4..8e5b0cac9615aa8588293d98fc93c820be550654 100644 --- a/compiler/GHC/Tc/Solver/Dict.hs +++ b/compiler/GHC/Tc/Solver/Dict.hs @@ -780,7 +780,7 @@ shortCutSolver dflags ev_w ev_i try_solve_from_instance (ev_binds, solved_dicts) ev | let pred = ctEvPred ev , ClassPred cls tys <- classifyPredType pred - = do { inst_res <- lift $ matchGlobalInst dflags True cls tys + = do { inst_res <- lift $ matchGlobalInst dflags True cls tys loc_w ; case inst_res of OneInst { cir_new_theta = preds , cir_mk_ev = mk_ev @@ -939,7 +939,7 @@ matchClassInst dflags inerts clas tys loc ; return local_res } NoInstance -- No local instances, so try global ones - -> do { global_res <- matchGlobalInst dflags False clas tys + -> do { global_res <- matchGlobalInst dflags False clas tys loc ; traceTcS "} matchClassInst global result" $ ppr global_res ; return global_res } } where diff --git a/compiler/GHC/Tc/Solver/Monad.hs b/compiler/GHC/Tc/Solver/Monad.hs index 71a473f1ae25177f9a091666cf467189e8c860dc..52d6d113ec5bd64ddb940245720f6a9f31b1f7f3 100644 --- a/compiler/GHC/Tc/Solver/Monad.hs +++ b/compiler/GHC/Tc/Solver/Monad.hs @@ -1641,9 +1641,9 @@ instFlexiXTcM subst (tv:tvs) matchGlobalInst :: DynFlags -> Bool -- True <=> caller is the short-cut solver -- See Note [Shortcut solving: overlap] - -> Class -> [Type] -> TcS TcM.ClsInstResult -matchGlobalInst dflags short_cut cls tys - = wrapTcS (TcM.matchGlobalInst dflags short_cut cls tys) + -> Class -> [Type] -> CtLoc -> TcS TcM.ClsInstResult +matchGlobalInst dflags short_cut cls tys loc + = wrapTcS $ TcM.setCtLocM loc $ TcM.matchGlobalInst dflags short_cut cls tys tcInstSkolTyVarsX :: SkolemInfo -> Subst -> [TyVar] -> TcS (Subst, [TcTyVar]) tcInstSkolTyVarsX skol_info subst tvs = wrapTcS $ TcM.tcInstSkolTyVarsX skol_info subst tvs diff --git a/compiler/GHC/Tc/TyCl/Utils.hs b/compiler/GHC/Tc/TyCl/Utils.hs index 767db8a7f7040dce4ce7749d8b7fd7bbd43ecfeb..6bcba860aaf145873fdc3e822d709544dd47ef07 100644 --- a/compiler/GHC/Tc/TyCl/Utils.hs +++ b/compiler/GHC/Tc/TyCl/Utils.hs @@ -885,14 +885,19 @@ mkOneRecordSelector all_cons idDetails fl has_sel sel_name = flSelector fl sel_id = mkExportedLocalId rec_details sel_name sel_ty - rec_details = RecSelId { sel_tycon = idDetails - , sel_naughty = is_naughty - , sel_fieldLabel = fl } -- Find a representative constructor, con1 - cons_w_field = conLikesWithFields all_cons [lbl] + cons_partitioned@(cons_w_field, _) = conLikesWithFields all_cons [lbl] con1 = assert (not (null cons_w_field)) $ head cons_w_field + -- Construct the IdDetails + rec_details = RecSelId { sel_tycon = idDetails + , sel_naughty = is_naughty + , sel_fieldLabel = fl + , sel_cons = cons_partitioned } + -- See Note [Detecting incomplete record selectors] in GHC.HsToCore.Pmc + + -- Selector type; Note [Polymorphic selectors] (univ_tvs, _, _, _, req_theta, _, data_ty) = conLikeFullSig con1 diff --git a/compiler/GHC/Types/Error.hs b/compiler/GHC/Types/Error.hs index c51d9ba59156ae4c2ecc59eda315f71dbc14efc6..c011e85d52e7b2f8a398a6342ca4f7dad37e963b 100644 --- a/compiler/GHC/Types/Error.hs +++ b/compiler/GHC/Types/Error.hs @@ -21,6 +21,7 @@ module GHC.Types.Error , addMessage , unionMessages , unionManyMessages + , filterMessages , MsgEnvelope (..) -- * Classifying Messages @@ -194,6 +195,10 @@ unionMessages (Messages msgs1) (Messages msgs2) = unionManyMessages :: Foldable f => f (Messages e) -> Messages e unionManyMessages = fold +filterMessages :: (MsgEnvelope e -> Bool) -> Messages e -> Messages e +filterMessages f (Messages msgs) = + Messages (filterBag f msgs) + -- | A 'DecoratedSDoc' is isomorphic to a '[SDoc]' but it carries the -- invariant that the input '[SDoc]' needs to be rendered /decorated/ into its -- final form, where the typical case would be adding bullets between each diff --git a/compiler/GHC/Types/Error/Codes.hs b/compiler/GHC/Types/Error/Codes.hs index 90a918c8f0206e4882078cabafe1e30647694a53..087b17054e5de7b36c65a894fbd1e4bda4a52d72 100644 --- a/compiler/GHC/Types/Error/Codes.hs +++ b/compiler/GHC/Types/Error/Codes.hs @@ -148,6 +148,7 @@ type family GhcDiagnosticCode c = n | n -> c where GhcDiagnosticCode "DsRecBindsNotAllowedForUnliftedTys" = 20185 GhcDiagnosticCode "DsRuleMightInlineFirst" = 95396 GhcDiagnosticCode "DsAnotherRuleMightFireFirst" = 87502 + GhcDiagnosticCode "DsIncompleteRecordSelector" = 17335 -- Parser diagnostic codes @@ -528,6 +529,7 @@ type family GhcDiagnosticCode c = n | n -> c where GhcDiagnosticCode "TcRnTyFamDepsDisabled" = 43991 GhcDiagnosticCode "TcRnAbstractClosedTyFamDecl" = 60012 GhcDiagnosticCode "TcRnPartialFieldSelector" = 82712 + GhcDiagnosticCode "TcRnHasFieldResolvedIncomplete" = 86894 GhcDiagnosticCode "TcRnSuperclassCycle" = 29210 GhcDiagnosticCode "TcRnDefaultSigMismatch" = 72771 GhcDiagnosticCode "TcRnTyFamResultDisabled" = 44012 diff --git a/compiler/GHC/Types/Id/Info.hs b/compiler/GHC/Types/Id/Info.hs index 281ac8657f9c757232c2b89393b0543a830555d7..798994a10b8688447731592022ec76e60b528d61 100644 --- a/compiler/GHC/Types/Id/Info.hs +++ b/compiler/GHC/Types/Id/Info.hs @@ -22,7 +22,7 @@ module GHC.Types.Id.Info ( IdDetails(..), pprIdDetails, coVarDetails, isCoVarDetails, JoinArity, isJoinIdDetails_maybe, RecSelParent(..), recSelParentName, recSelFirstConName, - idDetailsConcreteTvs, + recSelParentCons, idDetailsConcreteTvs, -- * The IdInfo type IdInfo, -- Abstract @@ -101,6 +101,7 @@ import GHC.Core.DataCon import GHC.Core.TyCon import GHC.Core.Type (mkTyConApp) import GHC.Core.PatSyn +import GHC.Core.ConLike import GHC.Types.ForeignCall import GHC.Unit.Module import GHC.Types.Demand @@ -149,7 +150,13 @@ data IdDetails , sel_fieldLabel :: FieldLabel , sel_naughty :: Bool -- True <=> a "naughty" selector which can't actually exist, for example @x@ in: -- data T = forall a. MkT { x :: a } - } -- See Note [Naughty record selectors] in GHC.Tc.TyCl + -- See Note [Naughty record selectors] in GHC.Tc.TyCl + , sel_cons :: ([ConLike], [ConLike]) + -- If record selector is not defined for all constructors + -- of a parent type, this is the pair of lists of constructors that + -- it is and is not defined for. Otherwise, it's Nothing. + -- Cached here based on the RecSelParent. + } -- See Note [Detecting incomplete record selectors] in GHC.HsToCore.Pmc | DataConWorkId DataCon -- ^ The 'Id' is for a data constructor /worker/ | DataConWrapId DataCon -- ^ The 'Id' is for a data constructor /wrapper/ @@ -332,6 +339,15 @@ recSelFirstConName :: RecSelParent -> Name recSelFirstConName (RecSelData tc) = dataConName $ head $ tyConDataCons tc recSelFirstConName (RecSelPatSyn ps) = patSynName ps +recSelParentCons :: RecSelParent -> [ConLike] +recSelParentCons (RecSelData tc) + | isAlgTyCon tc + = map RealDataCon $ visibleDataCons + $ algTyConRhs tc + | otherwise + = [] +recSelParentCons (RecSelPatSyn ps) = [PatSynCon ps] + instance Outputable RecSelParent where ppr p = case p of RecSelData tc diff --git a/compiler/GHC/Types/TyThing.hs b/compiler/GHC/Types/TyThing.hs index 83daf1576ee4f26e9e7d3c0b61da9c2746829e97..1be2095bfcae8f2fa9a07ea5ae12a5baeb4b9c3a 100644 --- a/compiler/GHC/Types/TyThing.hs +++ b/compiler/GHC/Types/TyThing.hs @@ -356,11 +356,11 @@ tyThingGREInfo = \case RecSelData tc -> let dcs = map RealDataCon $ tyConDataCons tc in case conLikesWithFields dcs [flLabel fl] of - [] -> pprPanic "tyThingGREInfo: no DataCons with this FieldLabel" $ + ([], _) -> pprPanic "tyThingGREInfo: no DataCons with this FieldLabel" $ vcat [ text "id:" <+> ppr id , text "fl:" <+> ppr fl , text "dcs:" <+> ppr dcs ] - cons -> mkUniqSet $ map conLikeConLikeName cons + (cons, _) -> mkUniqSet $ map conLikeConLikeName cons in IAmRecField $ RecFieldInfo { recFieldLabel = fl diff --git a/docs/users_guide/9.10.1-notes.rst b/docs/users_guide/9.10.1-notes.rst index 57fbd5177bd5602b4f79c0f21039382e5b9136dd..2021a2e14d429ea0c76d781bb70e62533a4ef1bf 100644 --- a/docs/users_guide/9.10.1-notes.rst +++ b/docs/users_guide/9.10.1-notes.rst @@ -21,6 +21,21 @@ Language Compiler ~~~~~~~~ +- GHC Proposal `#516 + <https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0516-incomplete-record-selectors.rst>`_ + has been implemented. It introduces a warning :ghc-flag:`-Wincomplete-record-selectors` which warns about when + an invocation of a record selector may fail due to being applied to a constructor for which it is not defined. + + For example :: + + data T = T1 | T2 { x :: Int } + f :: T -> Int + f a = x a + 1 -- emit a warning here, since `f T1` will fail + + Unlike :ghc-flag:`-Wpartial-fields` this produces a warning about incomplete selectors at use sites instead of + definition sites, so it is useful in cases when the library does intend for incomplete record selectors to be + used but only in specific circumstances (e.g. when other cases are handled by previous pattern matches). + GHCi ~~~~ diff --git a/docs/users_guide/9.8.1-notes.rst b/docs/users_guide/9.8.1-notes.rst index a7c2ad4eed68de339ff0637e23d61da698f37726..9f3cddb200c7aa9c8e4a64f2826a0bff2ac78393 100644 --- a/docs/users_guide/9.8.1-notes.rst +++ b/docs/users_guide/9.8.1-notes.rst @@ -177,7 +177,7 @@ Compiler {-# WARNING "do not use that constructor" D(D1), D(D2) ) - D = D1 | D2 + data D = D1 | D2 This allows for changing the structure of a library without immediately breaking user code, but instead being able to warn the user that a change in the library interface diff --git a/docs/users_guide/using-warnings.rst b/docs/users_guide/using-warnings.rst index 37ae36ae50a551b4cad78d17fefeb26763228e34..ec005d7a91e049afe56816254348089ac514fb4f 100644 --- a/docs/users_guide/using-warnings.rst +++ b/docs/users_guide/using-warnings.rst @@ -120,6 +120,7 @@ as ``-Wno-...`` for every individual warning in the group. * :ghc-flag:`-Whi-shadowing` * :ghc-flag:`-Wincomplete-record-updates` + * :ghc-flag:`-Wincomplete-record-selectors` * :ghc-flag:`-Wincomplete-uni-patterns` * :ghc-flag:`-Wmissing-pattern-synonym-signatures` * :ghc-flag:`-Wmissing-signatures` @@ -1079,6 +1080,39 @@ of ``-W(no-)*``. This option isn't enabled by default because it can be very noisy, and it often doesn't indicate a bug in the program. +.. ghc-flag:: -Wincomplete-record-selectors + :shortdesc: warn when a record selector application could fail + :type: dynamic + :reverse: -Wno-incomplete-record-selectors + :category: + + :since: 9.10 + + .. index:: + single: incomplete record selectors, warning + single: record selectors, incomplete + + When a record selector is applied to a constructor that does not + contain that field, it will produce an error. For example :: + + data T = T1 | T2 { x :: Int } + + f :: T -> Int + f a = x a -- `f T1` will fail + + g1 :: HasField "x" t Int => t -> Int + g1 a = 1 + getField @"x" a + + g2 :: T -> Int + g2 a = g1 a + 2 -- `g2 T1` will fail as well + + The warning warns about cases like that. It also takes into account + previously pattern-matched cases, for example :: + + d :: T -> Int + d T1 = 0 + d a = x a -- would not warn + .. ghc-flag:: -Wmissing-deriving-strategies :shortdesc: warn when a deriving clause is missing a deriving strategy :type: dynamic @@ -2480,13 +2514,13 @@ sanity, not yours.) <https://github.com/ghc-proposals/ghc-proposals/blob/master/proposals/0134-deprecating-exports-proposal.rst>`__, it is now possible to deprecate certain exports of a name without deprecating the name itself. - As explained in :ref:`warning-deprecated-pragma`, when a name is exported in several ways in the same module, + As explained in :ref:`warning-deprecated-pragma`, when a name is exported in several ways in the same module, but only some of those ways have a warning, it will not end up deprecated when imported in another module. For example: :: - + module A (x) where - + x :: Int x = 2 @@ -2496,5 +2530,5 @@ sanity, not yours.) ) import A - When :ghc-flag:`-Wincomplete-export-warnings` is enabled, GHC warns about exports + When :ghc-flag:`-Wincomplete-export-warnings` is enabled, GHC warns about exports that are not deprecating a name that is deprecated with another export in that module. \ No newline at end of file diff --git a/testsuite/tests/plugins/all.T b/testsuite/tests/plugins/all.T index 326fe9b31fe8cf4ae68847f8b909352d5c88a129..98c55bfc607b1f79a238a2f28af86c0a03c0e482 100644 --- a/testsuite/tests/plugins/all.T +++ b/testsuite/tests/plugins/all.T @@ -11,6 +11,21 @@ setTestOpts([ when(opsys('mingw32'), multi_cpu_race), ]) +import itertools + +# Check the simple-plugin tests without +# caring about the order of loading of interfaces +def normalizeIfaces(unnormalized_str): + iface_plugin_prefix = "interfacePlugin: " + grouped_plugins = itertools.groupby( + unnormalized_str.split('\n'), + lambda l: l.startswith(iface_plugin_prefix) + ) + return "\n".join([ + line + for k, lines in grouped_plugins + for line in (sorted(lines) if k else lines) + ]) test('plugins01', [extra_files(['simple-plugin/']), @@ -175,9 +190,11 @@ test('static-plugins', [extra_files(['simple-plugin/']), unless(config.have_RTS_linker, skip), expect_broken_for(16803, prof_ways), - extra_run_opts('"' + config.libdir + '"')], + extra_run_opts('"' + config.libdir + '"'), + normalise_fun(normalizeIfaces) + ], compile_and_run, - ['-package ghc -isimple-plugin/']) + ['-package ghc -isimple-plugin/ -j1']) test('T15858', [extra_files(['plugin-recomp/', 'plugin-recomp-test.hs']), diff --git a/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel1.hs b/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel1.hs new file mode 100644 index 0000000000000000000000000000000000000000..9c080702facac3d7422a51358f49d2c46611ff30 --- /dev/null +++ b/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel1.hs @@ -0,0 +1,25 @@ +-- Case for general occurences of selectors +{-# LANGUAGE PatternSynonyms #-} +module DsIncompleteRecSel1 where + +data T = T1 { x :: Bool } | T2 + +f :: T -> Bool +f = x + +f2 :: T -> Bool +f2 T2 = True +f2 a = d + where + d = x a + +f3 :: T -> Bool +f3 T2 = False +f3 a = x (let b = a in b) + + +pattern N{n} = (n :: Int) + +nat :: Int -> Int +nat 0 = 1 +nat a@N{} = n a \ No newline at end of file diff --git a/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel1.stderr b/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel1.stderr new file mode 100644 index 0000000000000000000000000000000000000000..48cbf7e07b6b0f7b7c2d3149a079fc926cec310f --- /dev/null +++ b/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel1.stderr @@ -0,0 +1,3 @@ + +DsIncompleteRecSel1.hs:8:5: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘x’ may fail for the following constructors: T2 diff --git a/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel2.hs b/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel2.hs new file mode 100644 index 0000000000000000000000000000000000000000..b18b6c2d64e9699be1a62d627966e22991dd8d9a --- /dev/null +++ b/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel2.hs @@ -0,0 +1,29 @@ +-- Case for long-range info and GADTs caught with PMC +{-# LANGUAGE GADTs #-} +{-# LANGUAGE PatternSynonyms #-} +module DsIncompleteRecSel2 where + +data T a where + T1 :: { x :: Bool } -> T a + T2 :: T Bool + T3 :: T Int + T4 :: Int -> T Bool + +f :: T Char -> Bool +f a = x a + +f2 :: T Int -> Bool +f2 T3 = True +f2 a = x a + +f3 :: T Bool -> Bool +f3 T2 = True +f3 (T4 1) = False +f3 a = x a + + +pattern G <- _ +pattern P <- T1 _ +f4 :: T Int -> Bool +f4 a@G | P <- a = x a +f4 _ = True \ No newline at end of file diff --git a/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel2.stderr b/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel2.stderr new file mode 100644 index 0000000000000000000000000000000000000000..b9522d273c18d93e7f0ef8e9fe15a681eaf557dc --- /dev/null +++ b/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel2.stderr @@ -0,0 +1,6 @@ + +DsIncompleteRecSel2.hs:22:8: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘x’ may fail for the following constructors: T4 + +DsIncompleteRecSel2.hs:28:19: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘x’ may fail for the following constructors: P diff --git a/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel3.hs b/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel3.hs new file mode 100644 index 0000000000000000000000000000000000000000..2cc4142f8da1721258ca7b95b8db96f9a6eeb9ef --- /dev/null +++ b/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel3.hs @@ -0,0 +1,37 @@ +module DsIncompleteRecSel3 where + +-- For exponentially specified nablas (with all combinations of True/False) +data T = T1{ x :: Bool } + | T2 + { a1 :: Bool + , a2 :: Bool + , a3 :: Bool + , a4 :: Bool + , a5 :: Bool + , a6 :: Bool + , a7 :: Bool + , a8 :: Bool + , a9 :: Bool + , a10 :: Bool + , a11 :: Bool + , a12 :: Bool + , a13 :: Bool + , a14 :: Bool + , a15 :: Bool + , a16 :: Bool + , a17 :: Bool + , a18 :: Bool + , a19 :: Bool + , a20 :: Bool } + +d :: T -> Bool +d T2{a2=True} = True +d t = x t + +data G = G1{ y :: Int } | G2 | G3 | G4 | G5 | G6 | G7 | G8 | G9 | G10 | G11 | G12 | G13 | G14 | G15 | G16 | G17 | G18 | G19 | G20 | G21 | G22 | G23 | G24 | G25 | G26 | G27 | G28 | G29 | G30 | G31 | G32 | G33 | G34 | G35 | G36 | G37 | G38 | G39 | G40 | G41 | G42 | G43 | G44 | G45 | G46 | G47 | G48 | G49 | G50 | G51 | G52 | G53 | G54 | G55 | G56 | G57 | G58 | G59 | G60 | G61 | G62 | G63 | G64 | G65 | G66 | G67 | G68 | G69 | G70 | G71 | G72 | G73 | G74 | G75 | G76 | G77 | G78 | G79 | G80 | G81 | G82 | G83 | G84 | G85 | G86 | G87 | G88 | G89 | G90 | G91 | G92 | G93 | G94 | G95 | G96 | G97 | G98 | G99 | G100 | G101 | G102 | G103 | G104 | G105 | G106 | G107 | G108 | G109 | G110 | G111 | G112 | G113 | G114 | G115 | G116 | G117 | G118 | G119 | G120 | G121 | G122 | G123 | G124 | G125 | G126 | G127 | G128 | G129 | G130 | G131 | G132 | G133 | G134 | G135 | G136 | G137 | G138 | G139 | G140 | G141 | G142 | G143 | G144 | G145 | G146 | G147 | G148 | G149 | G150 | G151 | G152 | G153 | G154 | G155 | G156 | G157 | G158 | G159 | G160 | G161 | G162 | G163 | G164 | G165 | G166 | G167 | G168 | G169 | G170 | G171 | G172 | G173 | G174 | G175 | G176 | G177 | G178 | G179 | G180 | G181 | G182 | G183 | G184 | G185 | G186 | G187 | G188 | G189 | G190 | G191 | G192 | G193 | G194 | G195 | G196 | G197 | G198 | G199 | G200 | G201 | G202 | G203 | G204 | G205 | G206 | G207 | G208 | G209 | G210 | G211 | G212 | G213 | G214 | G215 | G216 | G217 | G218 | G219 | G220 | G221 | G222 | G223 | G224 | G225 | G226 | G227 | G228 | G229 | G230 | G231 | G232 | G233 | G234 | G235 | G236 | G237 | G238 | G239 | G240 | G241 | G242 | G243 | G244 | G245 | G246 | G247 | G248 | G249 | G250 | G251 | G252 | G253 | G254 | G255 | G256 | G257 | G258 | G259 | G260 | G261 | G262 | G263 | G264 | G265 | G266 | G267 | G268 | G269 | G270 | G271 | G272 | G273 | G274 | G275 | G276 | G277 | G278 | G279 | G280 | G281 | G282 | G283 | G284 | G285 | G286 | G287 | G288 | G289 | G290 | G291 | G292 | G293 | G294 | G295 | G296 | G297 | G298 | G299 | G300 | G301 | G302 | G303 | G304 | G305 | G306 | G307 | G308 | G309 | G310 | G311 | G312 | G313 | G314 | G315 | G316 | G317 | G318 | G319 | G320 | G321 | G322 | G323 | G324 | G325 | G326 | G327 | G328 | G329 | G330 | G331 | G332 | G333 | G334 | G335 | G336 | G337 | G338 | G339 | G340 | G341 | G342 | G343 | G344 | G345 | G346 | G347 | G348 | G349 | G350 | G351 | G352 | G353 | G354 | G355 | G356 | G357 | G358 | G359 | G360 | G361 | G362 | G363 | G364 | G365 | G366 | G367 | G368 | G369 | G370 | G371 | G372 | G373 | G374 | G375 | G376 | G377 | G378 | G379 | G380 | G381 | G382 | G383 | G384 | G385 | G386 | G387 | G388 | G389 | G390 | G391 | G392 | G393 | G394 | G395 | G396 | G397 | G398 | G399 | G400 | G401 | G402 | G403 | G404 | G405 | G406 | G407 | G408 | G409 | G410 | G411 | G412 | G413 | G414 | G415 | G416 | G417 | G418 | G419 | G420 | G421 | G422 | G423 | G424 | G425 | G426 | G427 | G428 | G429 | G430 | G431 | G432 | G433 | G434 | G435 | G436 | G437 | G438 | G439 | G440 | G441 | G442 | G443 | G444 | G445 | G446 | G447 | G448 | G449 | G450 | G451 | G452 | G453 | G454 | G455 | G456 | G457 | G458 | G459 | G460 | G461 | G462 | G463 | G464 | G465 | G466 | G467 | G468 | G469 | G470 | G471 | G472 | G473 | G474 | G475 | G476 | G477 | G478 | G479 | G480 | G481 | G482 | G483 | G484 | G485 | G486 | G487 | G488 | G489 | G490 | G491 | G492 | G493 | G494 | G495 | G496 | G497 | G498 | G499 | G500 | G501 | G502 | G503 | G504 | G505 | G506 | G507 | G508 | G509 | G510 | G511 | G512 | G513 | G514 | G515 | G516 | G517 | G518 | G519 | G520 | G521 | G522 | G523 | G524 | G525 | G526 | G527 | G528 | G529 | G530 | G531 | G532 | G533 | G534 | G535 | G536 | G537 | G538 | G539 | G540 | G541 | G542 | G543 | G544 | G545 | G546 | G547 | G548 | G549 | G550 | G551 | G552 | G553 | G554 | G555 | G556 | G557 | G558 | G559 | G560 | G561 | G562 | G563 | G564 | G565 | G566 | G567 | G568 | G569 | G570 | G571 | G572 | G573 | G574 | G575 | G576 | G577 | G578 | G579 | G580 | G581 | G582 | G583 | G584 | G585 | G586 | G587 | G588 | G589 | G590 | G591 | G592 | G593 | G594 | G595 | G596 | G597 | G598 | G599 | G600 | G601 | G602 | G603 | G604 | G605 | G606 | G607 | G608 | G609 | G610 | G611 | G612 | G613 | G614 | G615 | G616 | G617 | G618 | G619 | G620 | G621 | G622 | G623 | G624 | G625 | G626 | G627 | G628 | G629 | G630 | G631 | G632 | G633 | G634 | G635 | G636 | G637 | G638 | G639 | G640 | G641 | G642 | G643 | G644 | G645 | G646 | G647 | G648 | G649 | G650 | G651 | G652 | G653 | G654 | G655 | G656 | G657 | G658 | G659 | G660 | G661 | G662 | G663 | G664 | G665 | G666 | G667 | G668 | G669 | G670 | G671 | G672 | G673 | G674 | G675 | G676 | G677 | G678 | G679 | G680 | G681 | G682 | G683 | G684 | G685 | G686 | G687 | G688 | G689 | G690 | G691 | G692 | G693 | G694 | G695 | G696 | G697 | G698 | G699 | G700 | G701 | G702 | G703 | G704 | G705 | G706 | G707 | G708 | G709 | G710 | G711 | G712 | G713 | G714 | G715 | G716 | G717 | G718 | G719 | G720 | G721 | G722 | G723 | G724 | G725 | G726 | G727 | G728 | G729 | G730 | G731 | G732 | G733 | G734 | G735 | G736 | G737 | G738 | G739 | G740 | G741 | G742 | G743 | G744 | G745 | G746 | G747 | G748 | G749 | G750 | G751 | G752 | G753 | G754 | G755 | G756 | G757 | G758 | G759 | G760 | G761 | G762 | G763 | G764 | G765 | G766 | G767 | G768 | G769 | G770 | G771 | G772 | G773 | G774 | G775 | G776 | G777 | G778 | G779 | G780 | G781 | G782 | G783 | G784 | G785 | G786 | G787 | G788 | G789 | G790 | G791 | G792 | G793 | G794 | G795 | G796 | G797 | G798 | G799 | G800 | G801 | G802 | G803 | G804 | G805 | G806 | G807 | G808 | G809 | G810 | G811 | G812 | G813 | G814 | G815 | G816 | G817 | G818 | G819 | G820 | G821 | G822 | G823 | G824 | G825 | G826 | G827 | G828 | G829 | G830 | G831 | G832 | G833 | G834 | G835 | G836 | G837 | G838 | G839 | G840 | G841 | G842 | G843 | G844 | G845 | G846 | G847 | G848 | G849 | G850 | G851 | G852 | G853 | G854 | G855 | G856 | G857 | G858 | G859 | G860 | G861 | G862 | G863 | G864 | G865 | G866 | G867 | G868 | G869 | G870 | G871 | G872 | G873 | G874 | G875 | G876 | G877 | G878 | G879 | G880 | G881 | G882 | G883 | G884 | G885 | G886 | G887 | G888 | G889 | G890 | G891 | G892 | G893 | G894 | G895 | G896 | G897 | G898 | G899 | G900 | G901 | G902 | G903 | G904 | G905 | G906 | G907 | G908 | G909 | G910 | G911 | G912 | G913 | G914 | G915 | G916 | G917 | G918 | G919 | G920 | G921 | G922 | G923 | G924 | G925 | G926 | G927 | G928 | G929 | G930 | G931 | G932 | G933 | G934 | G935 | G936 | G937 | G938 | G939 | G940 | G941 | G942 | G943 | G944 | G945 | G946 | G947 | G948 | G949 | G950 | G951 | G952 | G953 | G954 | G955 | G956 | G957 | G958 | G959 | G960 | G961 | G962 | G963 | G964 | G965 | G966 | G967 | G968 | G969 | G970 | G971 | G972 | G973 | G974 | G975 | G976 | G977 | G978 | G979 | G980 | G981 | G982 | G983 | G984 | G985 | G986 | G987 | G988 | G989 | G990 | G991 | G992 | G993 | G994 | G995 | G996 | G997 | G998 | G999 | G1000 + +g :: G -> Int +g a = y a + y a + y a + y a + y a + y a + y a + y a + y a + y a + +z :: G -> Int +z = y \ No newline at end of file diff --git a/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel3.stderr b/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel3.stderr new file mode 100644 index 0000000000000000000000000000000000000000..62637730c2bce1087b15ef0d8d61f4995f313e80 --- /dev/null +++ b/testsuite/tests/pmcheck/should_compile/DsIncompleteRecSel3.stderr @@ -0,0 +1,80 @@ + +DsIncompleteRecSel3.hs:29:7: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘x’ may fail for the following constructors: T2 + +DsIncompleteRecSel3.hs:34:7: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘y’ may fail for the following constructors: G2 + G3 + G4 + G5 + ... + +DsIncompleteRecSel3.hs:34:13: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘y’ may fail for the following constructors: G2 + G3 + G4 + G5 + ... + +DsIncompleteRecSel3.hs:34:19: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘y’ may fail for the following constructors: G2 + G3 + G4 + G5 + ... + +DsIncompleteRecSel3.hs:34:25: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘y’ may fail for the following constructors: G2 + G3 + G4 + G5 + ... + +DsIncompleteRecSel3.hs:34:31: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘y’ may fail for the following constructors: G2 + G3 + G4 + G5 + ... + +DsIncompleteRecSel3.hs:34:37: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘y’ may fail for the following constructors: G2 + G3 + G4 + G5 + ... + +DsIncompleteRecSel3.hs:34:43: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘y’ may fail for the following constructors: G2 + G3 + G4 + G5 + ... + +DsIncompleteRecSel3.hs:34:49: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘y’ may fail for the following constructors: G2 + G3 + G4 + G5 + ... + +DsIncompleteRecSel3.hs:34:55: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘y’ may fail for the following constructors: G2 + G3 + G4 + G5 + ... + +DsIncompleteRecSel3.hs:34:61: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘y’ may fail for the following constructors: G2 + G3 + G4 + G5 + ... + +DsIncompleteRecSel3.hs:37:5: warning: [GHC-17335] [-Wincomplete-record-selectors] + The application of the record field ‘y’ may fail for the following constructors: G2 + G3 + G4 + G5 + ... diff --git a/testsuite/tests/pmcheck/should_compile/all.T b/testsuite/tests/pmcheck/should_compile/all.T index 0030a9f2d74b39c9bbb5be634ea0b18010d67a80..75d11e1641ac8d1ca95cc9dac013fbcea41f37d6 100644 --- a/testsuite/tests/pmcheck/should_compile/all.T +++ b/testsuite/tests/pmcheck/should_compile/all.T @@ -164,4 +164,6 @@ test('EmptyCase007', [], compile, [overlapping_incomplete]) test('EmptyCase008', [], compile, [overlapping_incomplete]) test('EmptyCase009', [], compile, [overlapping_incomplete]) test('EmptyCase010', [], compile, [overlapping_incomplete]) - +test('DsIncompleteRecSel1', normal, compile, ['-Wincomplete-record-selectors']) +test('DsIncompleteRecSel2', normal, compile, ['-Wincomplete-record-selectors']) +test('DsIncompleteRecSel3', [collect_compiler_stats('bytes allocated', 10)], compile, ['-Wincomplete-record-selectors']) \ No newline at end of file diff --git a/testsuite/tests/typecheck/should_compile/TcIncompleteRecSel.hs b/testsuite/tests/typecheck/should_compile/TcIncompleteRecSel.hs new file mode 100644 index 0000000000000000000000000000000000000000..d7d26dda02df54406ea5c6d22f6944a3cb2ea966 --- /dev/null +++ b/testsuite/tests/typecheck/should_compile/TcIncompleteRecSel.hs @@ -0,0 +1,16 @@ +{-# LANGUAGE GADTs #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TypeApplications #-} +module TcIncompleteRecSel where + +import GHC.Records + +data T where + T1 :: { x :: Bool } -> T + T2 :: T + +f :: HasField "x" t Bool => t -> Bool +f = getField @"x" + +g :: T -> Bool +g = f \ No newline at end of file diff --git a/testsuite/tests/typecheck/should_compile/TcIncompleteRecSel.stderr b/testsuite/tests/typecheck/should_compile/TcIncompleteRecSel.stderr new file mode 100644 index 0000000000000000000000000000000000000000..1fe36df6f987c5d42aba142943fe8b2ca995c8fe --- /dev/null +++ b/testsuite/tests/typecheck/should_compile/TcIncompleteRecSel.stderr @@ -0,0 +1,3 @@ + +TcIncompleteRecSel.hs:16:5: warning: [GHC-86894] [-Wincomplete-record-selectors] + The invocation of `getField` on the record field ‘x’ may produce an error since it is not defined for all data constructors diff --git a/testsuite/tests/typecheck/should_compile/all.T b/testsuite/tests/typecheck/should_compile/all.T index 468de12a39371c6b1e37fbf259aa8687312f59e8..dbc9b6da60e8c816ccc4959aceb00a232245e017 100644 --- a/testsuite/tests/typecheck/should_compile/all.T +++ b/testsuite/tests/typecheck/should_compile/all.T @@ -889,3 +889,4 @@ test('T22537', normal, compile, ['']) test('T18986a', normal, compile, ['']) test('T18986b', normal, compile, ['']) test('T23413', normal, compile, ['']) +test('TcIncompleteRecSel', normal, compile, ['-Wincomplete-record-selectors'])