From 4fba42ef52d28e996ab65ca8efb50159b6f0423f Mon Sep 17 00:00:00 2001 From: Ben Gamari <ben@smart-cactus.org> Date: Sat, 11 Mar 2023 11:51:04 -0500 Subject: [PATCH] compiler: Default and warn ExceptionContext constraints --- compiler/GHC/Builtin/Names.hs | 19 ++++++ compiler/GHC/Core/Predicate.hs | 23 +++++++ compiler/GHC/Driver/Flags.hs | 2 + compiler/GHC/Driver/Session.hs | 1 + compiler/GHC/Tc/Errors/Ppr.hs | 15 +++++ compiler/GHC/Tc/Errors/Types.hs | 8 +++ compiler/GHC/Tc/Solver.hs | 65 +++++++++++++------ compiler/GHC/Types/Error/Codes.hs | 1 + docs/users_guide/using-warnings.rst | 17 +++++ .../src/GHC/Internal/Exception/Context.hs | 3 + .../WarnDefaultedExceptionContext.hs | 9 +++ .../WarnDefaultedExceptionContext.stderr | 6 ++ .../tests/typecheck/should_compile/all.T | 1 + .../should_run/DefaultExceptionContext.hs | 20 ++++++ testsuite/tests/typecheck/should_run/all.T | 1 + 15 files changed, 171 insertions(+), 20 deletions(-) create mode 100644 testsuite/tests/typecheck/should_compile/WarnDefaultedExceptionContext.hs create mode 100644 testsuite/tests/typecheck/should_compile/WarnDefaultedExceptionContext.stderr create mode 100644 testsuite/tests/typecheck/should_run/DefaultExceptionContext.hs diff --git a/compiler/GHC/Builtin/Names.hs b/compiler/GHC/Builtin/Names.hs index 54ceb0ae16e3..363b3024fc0c 100644 --- a/compiler/GHC/Builtin/Names.hs +++ b/compiler/GHC/Builtin/Names.hs @@ -456,6 +456,10 @@ basicKnownKeyNames -- Overloaded record fields hasFieldClassName, + -- ExceptionContext + exceptionContextTyConName, + emptyExceptionContextName, + -- Call Stacks callStackTyConName, emptyCallStackName, pushCallStackName, @@ -577,6 +581,7 @@ gHC_INTERNAL_BASE, gHC_INTERNAL_ENUM, gHC_INTERNAL_SHOW, gHC_INTERNAL_READ, gHC_INTERNAL_NUM, gHC_INTERNAL_MAYBE, gHC_INTERNAL_LIST, gHC_INTERNAL_TUPLE, gHC_INTERNAL_DATA_EITHER, gHC_INTERNAL_DATA_FOLDABLE, gHC_INTERNAL_DATA_TRAVERSABLE, + gHC_INTERNAL_EXCEPTION_CONTEXT, gHC_INTERNAL_CONC, gHC_INTERNAL_IO, gHC_INTERNAL_IO_Exception, gHC_INTERNAL_ST, gHC_INTERNAL_IX, gHC_INTERNAL_STABLE, gHC_INTERNAL_PTR, gHC_INTERNAL_ERR, gHC_INTERNAL_REAL, gHC_INTERNAL_FLOAT, gHC_INTERNAL_TOP_HANDLER, gHC_INTERNAL_SYSTEM_IO, gHC_INTERNAL_DYNAMIC, @@ -628,6 +633,7 @@ gHC_INTERNAL_RANDOM = mkGhcInternalModule (fsLit "GHC.Internal.S gHC_INTERNAL_EXTS = mkGhcInternalModule (fsLit "GHC.Internal.Exts") gHC_INTERNAL_IS_LIST = mkGhcInternalModule (fsLit "GHC.Internal.IsList") gHC_INTERNAL_CONTROL_EXCEPTION_BASE = mkGhcInternalModule (fsLit "GHC.Internal.Control.Exception.Base") +gHC_INTERNAL_EXCEPTION_CONTEXT = mkGhcInternalModule (fsLit "GHC.Internal.Exception.Context") gHC_INTERNAL_GENERICS = mkGhcInternalModule (fsLit "GHC.Internal.Generics") gHC_INTERNAL_TYPEERROR = mkGhcInternalModule (fsLit "GHC.Internal.TypeError") gHC_INTERNAL_TYPELITS = mkGhcInternalModule (fsLit "GHC.Internal.TypeLits") @@ -1615,6 +1621,13 @@ hasFieldClassName :: Name hasFieldClassName = clsQual gHC_INTERNAL_RECORDS (fsLit "HasField") hasFieldClassNameKey +-- ExceptionContext +exceptionContextTyConName, emptyExceptionContextName :: Name +exceptionContextTyConName = + tcQual gHC_INTERNAL_EXCEPTION_CONTEXT (fsLit "ExceptionContext") exceptionContextTyConKey +emptyExceptionContextName + = varQual gHC_INTERNAL_EXCEPTION_CONTEXT (fsLit "emptyExceptionContext") emptyExceptionContextKey + -- Source Locations callStackTyConName, emptyCallStackName, pushCallStackName, srcLocDataConName :: Name @@ -2094,6 +2107,9 @@ constPtrTyConKey = mkPreludeTyConUnique 417 jsvalTyConKey = mkPreludeTyConUnique 418 +exceptionContextTyConKey :: Unique +exceptionContextTyConKey = mkPreludeTyConUnique 420 + {- ************************************************************************ * * @@ -2544,6 +2560,9 @@ fromStaticPtrClassOpKey = mkPreludeMiscIdUnique 560 makeStaticKey :: Unique makeStaticKey = mkPreludeMiscIdUnique 561 +emptyExceptionContextKey :: Unique +emptyExceptionContextKey = mkPreludeMiscIdUnique 562 + -- Unsafe coercion proofs unsafeEqualityProofIdKey, unsafeCoercePrimIdKey :: Unique unsafeEqualityProofIdKey = mkPreludeMiscIdUnique 570 diff --git a/compiler/GHC/Core/Predicate.hs b/compiler/GHC/Core/Predicate.hs index be9195b27b2f..12767b9956ac 100644 --- a/compiler/GHC/Core/Predicate.hs +++ b/compiler/GHC/Core/Predicate.hs @@ -27,6 +27,7 @@ module GHC.Core.Predicate ( -- Implicit parameters isIPLikePred, mentionsIP, isIPTyCon, isIPClass, isCallStackTy, isCallStackPred, isCallStackPredTy, + isExceptionContextPred, isIPPred_maybe, -- Evidence variables @@ -277,6 +278,28 @@ isIPPred_maybe cls tys | otherwise = Nothing +-- --------------------- ExceptionContext predicates -------------------------- + +-- | Is a 'PredType' an @ExceptionContext@ implicit parameter? +-- +-- If so, return the name of the parameter. +isExceptionContextPred :: Class -> [Type] -> Maybe FastString +isExceptionContextPred cls tys + | [ty1, ty2] <- tys + , isIPClass cls + , isExceptionContextTy ty2 + = isStrLitTy ty1 + | otherwise + = Nothing + +-- | Is a type a 'CallStack'? +isExceptionContextTy :: Type -> Bool +isExceptionContextTy ty + | Just tc <- tyConAppTyCon_maybe ty + = tc `hasKey` exceptionContextTyConKey + | otherwise + = False + -- --------------------- CallStack predicates --------------------------------- isCallStackPredTy :: Type -> Bool diff --git a/compiler/GHC/Driver/Flags.hs b/compiler/GHC/Driver/Flags.hs index 070a09394473..39c8fb330cff 100644 --- a/compiler/GHC/Driver/Flags.hs +++ b/compiler/GHC/Driver/Flags.hs @@ -706,6 +706,7 @@ data WarningFlag = | Opt_WarnInconsistentFlags -- Since 9.8 | Opt_WarnDataKindsTC -- Since 9.10 | Opt_WarnDeprecatedTypeAbstractions -- Since 9.10 + | Opt_WarnDefaultedExceptionContext -- Since 9.10 deriving (Eq, Ord, Show, Enum, Bounded) -- | Return the names of a WarningFlag @@ -821,6 +822,7 @@ warnFlagNames wflag = case wflag of Opt_WarnInconsistentFlags -> "inconsistent-flags" :| [] Opt_WarnDataKindsTC -> "data-kinds-tc" :| [] Opt_WarnDeprecatedTypeAbstractions -> "deprecated-type-abstractions" :| [] + Opt_WarnDefaultedExceptionContext -> "defaulted-exception-context" :| [] -- ----------------------------------------------------------------------------- -- Standard sets of warning options diff --git a/compiler/GHC/Driver/Session.hs b/compiler/GHC/Driver/Session.hs index 228918452543..b8eaf91f37b7 100644 --- a/compiler/GHC/Driver/Session.hs +++ b/compiler/GHC/Driver/Session.hs @@ -2301,6 +2301,7 @@ wWarningFlagsDeps = [minBound..maxBound] >>= \x -> case x of Opt_WarnIncompleteRecordSelectors -> warnSpec x Opt_WarnDataKindsTC -> warnSpec x Opt_WarnDeprecatedTypeAbstractions -> warnSpec x + Opt_WarnDefaultedExceptionContext -> warnSpec x warningGroupsDeps :: [(Deprecation, FlagSpec WarningGroup)] warningGroupsDeps = map mk warningGroups diff --git a/compiler/GHC/Tc/Errors/Ppr.hs b/compiler/GHC/Tc/Errors/Ppr.hs index 01b7f6b4af73..ef4fe07f8818 100644 --- a/compiler/GHC/Tc/Errors/Ppr.hs +++ b/compiler/GHC/Tc/Errors/Ppr.hs @@ -1806,6 +1806,17 @@ instance Diagnostic TcRnMessage where TcRnNonCanonicalDefinition reason inst_ty -> mkSimpleDecorated $ pprNonCanonicalDefinition inst_ty reason + TcRnDefaultedExceptionContext ct_loc -> + mkSimpleDecorated $ vcat [ header, warning, proposal ] + where + header, warning, proposal :: SDoc + header + = vcat [ text "Solving for an implicit ExceptionContext constraint" + , nest 2 $ pprCtOrigin (ctLocOrigin ct_loc) <> text "." ] + warning + = vcat [ text "Future versions of GHC will turn this warning into an error." ] + proposal + = vcat [ text "See GHC Proposal #330." ] TcRnImplicitImportOfPrelude -> mkSimpleDecorated $ text "Module" <+> quotes (text "Prelude") <+> text "implicitly imported." @@ -2503,6 +2514,8 @@ instance Diagnostic TcRnMessage where -> WarningWithFlag Opt_WarnNonCanonicalMonoidInstances TcRnNonCanonicalDefinition (NonCanonicalMonad _) _ -> WarningWithFlag Opt_WarnNonCanonicalMonadInstances + TcRnDefaultedExceptionContext{} + -> WarningWithFlag Opt_WarnDefaultedExceptionContext TcRnImplicitImportOfPrelude {} -> WarningWithFlag Opt_WarnImplicitPrelude TcRnMissingMain {} @@ -3169,6 +3182,8 @@ instance Diagnostic TcRnMessage where -> noHints TcRnNonCanonicalDefinition reason _ -> suggestNonCanonicalDefinition reason + TcRnDefaultedExceptionContext _ + -> noHints TcRnImplicitImportOfPrelude {} -> noHints TcRnMissingMain {} diff --git a/compiler/GHC/Tc/Errors/Types.hs b/compiler/GHC/Tc/Errors/Types.hs index ec2e4aa3e2a5..d313a844f591 100644 --- a/compiler/GHC/Tc/Errors/Types.hs +++ b/compiler/GHC/Tc/Errors/Types.hs @@ -4263,6 +4263,14 @@ data TcRnMessage where -} TcRnNamespacedFixitySigWithoutFlag :: FixitySig GhcPs -> TcRnMessage + {-| TcRnDefaultedExceptionContext is a warning that is triggered when the + backward-compatibility logic solving for implicit ExceptionContext + constraints fires. + + Test cases: DefaultExceptionContext + -} + TcRnDefaultedExceptionContext :: CtLoc -> TcRnMessage + deriving Generic ---- diff --git a/compiler/GHC/Tc/Solver.hs b/compiler/GHC/Tc/Solver.hs index 947cdf88ca7d..f29451b0259d 100644 --- a/compiler/GHC/Tc/Solver.hs +++ b/compiler/GHC/Tc/Solver.hs @@ -68,6 +68,7 @@ import GHC.Core.Unify ( tcMatchTyKis ) import GHC.Unit.Module ( getModule ) import GHC.Utils.Misc import GHC.Utils.Panic +import GHC.Types.TyThing ( MonadThings(lookupId) ) import GHC.Types.Var import GHC.Types.Var.Env import GHC.Types.Var.Set @@ -83,7 +84,7 @@ import Data.Foldable ( toList, traverse_ ) import Data.List ( partition ) import Data.List.NonEmpty ( NonEmpty(..), nonEmpty ) import qualified Data.List.NonEmpty as NE -import GHC.Data.Maybe ( mapMaybe ) +import GHC.Data.Maybe ( mapMaybe, runMaybeT, MaybeT ) {- ********************************************************************************* @@ -548,10 +549,7 @@ simplifyTopWanteds wanteds try_callstack_defaulting :: WantedConstraints -> TcS WantedConstraints try_callstack_defaulting wc - | isEmptyWC wc - = return wc - | otherwise - = defaultCallStacks wc + = defaultConstraints [defaultCallStack, defaultExceptionContext] wc -- | If an implication contains a Given of the form @Unsatisfiable msg@, use -- it to solve all Wanteds within the implication. @@ -696,19 +694,56 @@ This allows us to indirectly box constraints with different representations (such as primitive equality constraints). -} +-- | A 'TcS' action which can may default a 'Ct'. +type CtDefaultingStrategy = Ct -> MaybeT TcS () + +-- | Default @ExceptionContext@ constraints to @emptyExceptionContext@. +defaultExceptionContext :: CtDefaultingStrategy +defaultExceptionContext ct + = do { ClassPred cls tys <- pure $ classifyPredType (ctPred ct) + ; Just {} <- pure $ isExceptionContextPred cls tys + ; emptyEC <- Var <$> lift (lookupId emptyExceptionContextName) + ; let ev = ctEvidence ct + ; let ev_tm = mkEvCast emptyEC (wrapIP (ctEvPred ev)) + ; lift $ warnTcS $ TcRnDefaultedExceptionContext (ctLoc ct) + ; lift $ setEvBindIfWanted ev False ev_tm + } + -- | Default any remaining @CallStack@ constraints to empty @CallStack@s. -defaultCallStacks :: WantedConstraints -> TcS WantedConstraints -- See Note [Overview of implicit CallStacks] in GHC.Tc.Types.Evidence -defaultCallStacks wanteds +defaultCallStack :: CtDefaultingStrategy +defaultCallStack ct + = do { ClassPred cls tys <- pure $ classifyPredType (ctPred ct) + ; Just {} <- pure $ isCallStackPred cls tys + ; lift $ solveCallStack (ctEvidence ct) EvCsEmpty + } + +defaultConstraints :: [CtDefaultingStrategy] + -> WantedConstraints + -> TcS WantedConstraints +-- See Note [Overview of implicit CallStacks] in GHC.Tc.Types.Evidence +defaultConstraints defaulting_strategies wanteds + | isEmptyWC wanteds = return wanteds + | otherwise = do simples <- handle_simples (wc_simple wanteds) mb_implics <- mapBagM handle_implic (wc_impl wanteds) return (wanteds { wc_simple = simples , wc_impl = catBagMaybes mb_implics }) where - + handle_simples :: Bag Ct -> TcS (Bag Ct) handle_simples simples - = catBagMaybes <$> mapBagM defaultCallStack simples + = catBagMaybes <$> mapBagM handle_simple simples + where + handle_simple :: Ct -> TcS (Maybe Ct) + handle_simple ct = go defaulting_strategies + where + go [] = return (Just ct) + go (f:fs) = do + mb <- runMaybeT (f ct) + case mb of + Just () -> return Nothing + Nothing -> go fs handle_implic :: Implication -> TcS (Maybe Implication) -- The Maybe is because solving the CallStack constraint @@ -720,19 +755,9 @@ defaultCallStacks wanteds = do { wanteds <- setEvBindsTcS (ic_binds implic) $ -- defaultCallStack sets a binding, so -- we must set the correct binding group - defaultCallStacks (ic_wanted implic) + defaultConstraints defaulting_strategies (ic_wanted implic) ; setImplicationStatus (implic { ic_wanted = wanteds }) } - defaultCallStack ct - | ClassPred cls tys <- classifyPredType (ctPred ct) - , Just {} <- isCallStackPred cls tys - = do { solveCallStack (ctEvidence ct) EvCsEmpty - ; return Nothing } - - defaultCallStack ct - = return (Just ct) - - {- Note [When to do type-class defaulting] ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ In GHC 7.6 and 7.8.2, we did type-class defaulting only if insolubleWC diff --git a/compiler/GHC/Types/Error/Codes.hs b/compiler/GHC/Types/Error/Codes.hs index 751bab158c1c..f45cf0fdfb81 100644 --- a/compiler/GHC/Types/Error/Codes.hs +++ b/compiler/GHC/Types/Error/Codes.hs @@ -588,6 +588,7 @@ type family GhcDiagnosticCode c = n | n -> c where GhcDiagnosticCode "TcRnBindingNameConflict" = 10498 GhcDiagnosticCode "NonCanonicalMonoid" = 50928 GhcDiagnosticCode "NonCanonicalMonad" = 22705 + GhcDiagnosticCode "TcRnDefaultedExceptionContext" = 46235 GhcDiagnosticCode "TcRnImplicitImportOfPrelude" = 20540 GhcDiagnosticCode "TcRnMissingMain" = 67120 GhcDiagnosticCode "TcRnGhciUnliftedBind" = 17999 diff --git a/docs/users_guide/using-warnings.rst b/docs/users_guide/using-warnings.rst index 6713c8f7b971..ce0db740e8c3 100644 --- a/docs/users_guide/using-warnings.rst +++ b/docs/users_guide/using-warnings.rst @@ -2594,6 +2594,23 @@ of ``-W(no-)*``. removed. Users can enable the :extension:`DataKinds` extension to avoid issues (thus silencing the warning). +.. ghc-flag:: -Wdefaulted-exception-context + :shortdesc: warn when an :base-ref:`Control.Exception.Context.ExceptionContext` + implicit parameter is defaulted to + :base-ref:`Control.Exception.Context.emptyExceptionContext`. + :type: dynamic + :reverse: -Wnop-defaulted-exception-context + + :since: 9.10.1 + + Introduced in GHC 9.10.1 with the introduction of an implicit + :base-ref:`Control.Exception.Context.ExceptionContext`` context to + :base-ref:`Control.Exception.SomeException`. To preserve compatibility + with earlier compilers, this constraints is implicitly defaulted to + :base-ref:`Control.Exception.Context.emptyExceptionContext` when no other + evidence is available. As this behavior may result in dropped exception context + this warning is provided to give notice when defaulting occurs. + If you're feeling really paranoid, the :ghc-flag:`-dcore-lint` option is a good choice. It turns on heavyweight intra-pass sanity-checking within GHC. (It checks GHC's sanity, not yours.) diff --git a/libraries/ghc-internal/src/GHC/Internal/Exception/Context.hs b/libraries/ghc-internal/src/GHC/Internal/Exception/Context.hs index 7a3a64c04f45..2ae71102c673 100644 --- a/libraries/ghc-internal/src/GHC/Internal/Exception/Context.hs +++ b/libraries/ghc-internal/src/GHC/Internal/Exception/Context.hs @@ -46,6 +46,9 @@ import GHC.Internal.Data.Type.Equality ( (:~~:)(HRefl) ) -- -- 'ExceptionContext's can be merged via concatenation using the 'Semigroup' -- instance or 'mergeExceptionContext'. +-- +-- Note that GHC will automatically solve implicit constraints of type 'ExceptionContext' +-- with 'emptyExceptionContext'. data ExceptionContext = ExceptionContext [SomeExceptionAnnotation] instance Semigroup ExceptionContext where diff --git a/testsuite/tests/typecheck/should_compile/WarnDefaultedExceptionContext.hs b/testsuite/tests/typecheck/should_compile/WarnDefaultedExceptionContext.hs new file mode 100644 index 000000000000..09a01af6acf7 --- /dev/null +++ b/testsuite/tests/typecheck/should_compile/WarnDefaultedExceptionContext.hs @@ -0,0 +1,9 @@ +module WarnDefaultedExceptionContext where + +import Control.Exception.Context +import Control.Exception + +-- The implicit ExceptionContext constraint here should be defaulted with a warning +exc :: SomeException +exc = SomeException (userError "uh oh") + diff --git a/testsuite/tests/typecheck/should_compile/WarnDefaultedExceptionContext.stderr b/testsuite/tests/typecheck/should_compile/WarnDefaultedExceptionContext.stderr new file mode 100644 index 000000000000..a868edc21462 --- /dev/null +++ b/testsuite/tests/typecheck/should_compile/WarnDefaultedExceptionContext.stderr @@ -0,0 +1,6 @@ + +WarnDefaultedExceptionContext.hs:1:1: warning: [GHC-46235] [-Wdefaulted-exception-context] + Solving for an implicit ExceptionContext constraint + arising from a use of ‘SomeException’. + Future versions of GHC will turn this warning into an error. + See GHC Proposal #330. diff --git a/testsuite/tests/typecheck/should_compile/all.T b/testsuite/tests/typecheck/should_compile/all.T index a2f4b3ea7521..0d3ff57dbeca 100644 --- a/testsuite/tests/typecheck/should_compile/all.T +++ b/testsuite/tests/typecheck/should_compile/all.T @@ -911,3 +911,4 @@ test('T22788', normal, compile, ['']) test('T21206', normal, compile, ['']) test('T17594a', req_th, compile, ['']) test('T17594f', normal, compile, ['']) +test('WarnDefaultedExceptionContext', normal, compile, ['-Wdefaulted-exception-context']) diff --git a/testsuite/tests/typecheck/should_run/DefaultExceptionContext.hs b/testsuite/tests/typecheck/should_run/DefaultExceptionContext.hs new file mode 100644 index 000000000000..42e3357dd4d5 --- /dev/null +++ b/testsuite/tests/typecheck/should_run/DefaultExceptionContext.hs @@ -0,0 +1,20 @@ +module Main where + +import Control.Exception.Context +import Control.Exception + +data TestError = TestError + deriving (Show) + +instance Exception TestError + +-- The implicit ExceptionContext constraint here should be defaulted +exc :: SomeException +exc = SomeException TestError + +main :: IO () +main = do + case getAllExceptionAnnotations (someExceptionContext exc) of + [] -> return () + _ -> fail "unexpected ExceptionContext" + diff --git a/testsuite/tests/typecheck/should_run/all.T b/testsuite/tests/typecheck/should_run/all.T index 4e189e0adb8e..f27df87c541e 100755 --- a/testsuite/tests/typecheck/should_run/all.T +++ b/testsuite/tests/typecheck/should_run/all.T @@ -178,3 +178,4 @@ test('T18324', normal, compile_and_run, ['']) test('T15598', normal, compile_and_run, ['']) test('T22086', normal, compile_and_run, ['']) test('T24411', normal, compile_and_run, ['']) +test('DefaultExceptionContext', normal, compile_and_run, ['']) -- GitLab