Remove custom type errors from Member (#429)

* Remove Member type errors

* Remove unused custom type errors

* Deprecate MemberWithError

* Remove readme notes about type errors

* Remove MemberWithError
This commit is contained in:
Sandy Maguire 2021-11-16 15:01:45 -08:00 committed by GitHub
parent ac431a17b4
commit 55150644c4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 19 additions and 386 deletions

View File

@ -195,12 +195,6 @@ makes the helpful suggestion:
$ \case
```
Likewise it will give you tips on what to do if you forget a `TypeApplication`
or forget to handle an effect.
Don't like helpful errors? That's OK too - just flip the `error-messages`
flag and enjoy the raw, unadulterated fury of the typesystem.
## Necessary Language Extensions
You're going to want to stick all of this into your `package.yaml` file.

View File

@ -63,10 +63,6 @@ flags:
description: Dump HTML for the core generated by GHC during compilation
default: False
manual: True
error-messages:
description: Provide custom error messages
default: True
manual: True
library:
ghc-options: -Wall
@ -87,13 +83,6 @@ library:
dependencies:
- unsupported-ghc-version > 1 && < 1
- condition: flag(error-messages)
then:
# dummy value because cabal is stupid
cpp-options: -DCABAL_SERIOUSLY_CMON_MATE
else:
cpp-options: -DNO_ERROR_MESSAGES
tests:
polysemy-test:
main: Main.hs

View File

@ -78,7 +78,6 @@ test-suite polysemy-plugin-test
main-is: Main.hs
other-modules:
AmbiguousSpec
BadSpec
DoctestSpec
ExampleSpec
InsertSpec

View File

@ -122,7 +122,7 @@ data FindConstraint = FindConstraint
-- | Given a list of constraints, filter out the 'FindConstraint's.
getFindConstraints :: PolysemyStuff 'Things -> [Ct] -> [FindConstraint]
getFindConstraints (findClass -> cls) cts = do
cd@CDictCan{cc_class = cls', cc_tyargs = [_, eff, r]} <- cts
cd@CDictCan{cc_class = cls', cc_tyargs = [eff, r]} <- cts
guard $ cls == cls'
pure $ FindConstraint
{ fcLoc = ctLoc cd
@ -229,53 +229,6 @@ mkWanted fc solve_ctx given =
wanted = fcEffect fc
------------------------------------------------------------------------------
-- | Given a list of 'Ct's, find any that are of the form
-- @[Irred] Sem r a ~ Something@, and return their @r@s.
getBogusRs :: PolysemyStuff 'Things -> [Ct] -> [Type]
getBogusRs stuff wanteds = do
CIrredCan ct _ <- wanteds
(_, [_, _, a, b]) <- pure . splitAppTys $ ctev_pred ct
maybeToList (extractRowFromSem stuff a)
++ maybeToList (extractRowFromSem stuff b)
------------------------------------------------------------------------------
-- | Take the @r@ out of @Sem r a@.
extractRowFromSem :: PolysemyStuff 'Things -> Type -> Maybe Type
extractRowFromSem (semTyCon -> sem) ty = do
(tycon, [r, _]) <- splitTyConApp_maybe ty
guard $ tycon == sem
pure r
------------------------------------------------------------------------------
-- | Given a list of bogus @r@s, and the wanted constraints, produce bogus
-- evidence terms that will prevent @IfStuck (LocateEffect _ r) _ _@ error messsages.
solveBogusError :: PolysemyStuff 'Things -> [Ct] -> [(EvTerm, Ct)]
solveBogusError stuff wanteds = do
let splitTyConApp_list = maybeToList . splitTyConApp_maybe
let bogus = getBogusRs stuff wanteds
ct@(CIrredCan ce _) <- wanteds
(stuck, [_, _, expr, _, _]) <- splitTyConApp_list $ ctev_pred ce
guard $ stuck == ifStuckTyCon stuff
(idx, [_, _, r]) <- splitTyConApp_list expr
guard $ idx == locateEffectTyCon stuff
guard $ elem @[] (OrdType r) $ coerce bogus
pure (error $ unlines
[ "Bogus proof for stuck type family."
, ""
, "This means there's a type error in your program, but the fact that"
, "you're seeing this message is a bug in `polysemy-plugin`."
, ""
, "Please file a bug at https://github.com/polysemy-research/polysemy"
, "with a minimal reproduction for how you managed to get this error."
]
, ct
)
------------------------------------------------------------------------------
-- | Determine if there is exactly one wanted find for the @r@ in question.
exactlyOneWantedForR
@ -369,5 +322,5 @@ solveFundep (ref, stuff) given _ wanted = do
let (unifications, new_wanteds) = unzipNewWanteds already_emitted $ catMaybes eqs
tcPluginIO $ modifyIORef ref $ S.union $ S.fromList unifications
pure $ TcPluginOk (solveBogusError stuff wanted) new_wanteds
pure $ TcPluginOk [] new_wanteds

View File

@ -15,14 +15,14 @@ import GHC.Types.Name.Occurrence (mkTcOcc)
import GHC.Tc.Plugin (TcPluginM, tcLookupClass, tcLookupTyCon, unsafeTcPluginTcM)
import GHC.Plugins (getDynFlags, unitState)
import GHC.Unit.State (lookupModuleWithSuggestions, LookupResult (..))
import GHC.Utils.Outputable (pprPanic, empty, text, (<+>), ($$))
import GHC.Utils.Outputable (pprPanic, text, (<+>), ($$))
#else
import FastString (fsLit)
import OccName (mkTcOcc)
import TcPluginM (TcPluginM, tcLookupClass, tcLookupTyCon, unsafeTcPluginTcM)
import GhcPlugins (getDynFlags)
import Packages (lookupModuleWithSuggestions, LookupResult (..))
import Outputable (pprPanic, empty, text, (<+>), ($$))
import Outputable (pprPanic, text, (<+>), ($$))
#endif
@ -34,8 +34,6 @@ import Outputable (pprPanic, empty, text, (<+>), ($$))
data PolysemyStuff (l :: LookupState) = PolysemyStuff
{ findClass :: ThingOf l Class
, semTyCon :: ThingOf l TyCon
, ifStuckTyCon :: ThingOf l TyCon
, locateEffectTyCon :: ThingOf l TyCon
}
@ -43,10 +41,8 @@ data PolysemyStuff (l :: LookupState) = PolysemyStuff
-- | All of the things we need to lookup.
polysemyStuffLocations :: PolysemyStuff 'Locations
polysemyStuffLocations = PolysemyStuff
{ findClass = ("Polysemy.Internal.Union", "Find")
, semTyCon = ("Polysemy.Internal", "Sem")
, ifStuckTyCon = ("Polysemy.Internal.CustomErrors.Redefined", "IfStuck")
, locateEffectTyCon = ("Polysemy.Internal.Union", "LocateEffect")
{ findClass = ("Polysemy.Internal.Union", "Member")
, semTyCon = ("Polysemy.Internal", "Sem")
}
@ -79,11 +75,9 @@ polysemyStuff = do
#endif
_ -> pure ()
let PolysemyStuff a b c d = polysemyStuffLocations
let PolysemyStuff a b = polysemyStuffLocations
PolysemyStuff <$> doLookup a
<*> doLookup b
<*> doLookup c
<*> doLookup d
------------------------------------------------------------------------------

View File

@ -1,9 +1,7 @@
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# OPTIONS_GHC -fdefer-type-errors #-}
{-# OPTIONS_GHC -fno-warn-deferred-type-errors #-}
{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}
{-# OPTIONS_GHC -fplugin=Polysemy.Plugin #-}
module AmbiguousSpec where
@ -38,12 +36,6 @@ uniquelyB = put $ mptc False
uniquelyIO :: Members '[Embed IO, Embed Identity] r => Sem r ()
uniquelyIO = embed $ liftIO $ pure ()
ambiguous1 :: Members '[State (Sum Int), State String] r => Sem r ()
ambiguous1 = put mempty
ambiguous2 :: (Num String, Members '[State Int, State String] r) => Sem r ()
ambiguous2 = put 10
spec :: Spec
spec = describe "example" $ do
@ -67,9 +59,3 @@ spec = describe "example" $ do
z <- runM . runEmbedded @Identity (pure . runIdentity) $ uniquelyIO
z `shouldBe` ()
it "should not typecheck ambiguous1" $ do
shouldNotTypecheck ambiguous1
it "should not typecheck ambiguous2" $ do
shouldNotTypecheck ambiguous2

View File

@ -1,38 +0,0 @@
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -fdefer-type-errors -fno-warn-deferred-type-errors #-}
module BadSpec where
import Polysemy
import Polysemy.State
import Test.Hspec
import Test.ShouldNotTypecheck
data KVStore k v m a where
GetKV :: k -> KVStore k v m (Maybe v)
makeSem ''KVStore
positivePos :: Member (KVStore k v) r => Sem r (Maybe v)
positivePos = do
getKV "hello"
negativePos :: Member (KVStore String v) r => Sem r (Maybe Bool)
negativePos = do
getKV "hello"
badState :: Member (State a) r => Sem r ()
badState = put ()
spec :: Spec
spec = do
describe "incorrectly polymorphic constraint" $ do
it "should not typecheck in positive position" $ do
shouldNotTypecheck positivePos
it "should not typecheck in negative position" $ do
shouldNotTypecheck negativePos
it "should not typecheck badly polymorphic State" $ do
shouldNotTypecheck badState

View File

@ -36,11 +36,6 @@ flag dump-core
manual: True
default: False
flag error-messages
description: Provide custom error messages
manual: True
default: True
library
exposed-modules:
Polysemy
@ -132,10 +127,6 @@ library
if impl(ghc < 8.2.2)
build-depends:
unsupported-ghc-version >1 && <1
if flag(error-messages)
cpp-options: -DCABAL_SERIOUSLY_CMON_MATE
else
cpp-options: -DNO_ERROR_MESSAGES
default-language: Haskell2010
test-suite polysemy-test

View File

@ -2,7 +2,6 @@ module Polysemy
( -- * Core Types
Sem ()
, Member
, MemberWithError
, Members
-- * Running Sem

View File

@ -11,7 +11,6 @@
module Polysemy.Internal
( Sem (..)
, Member
, MemberWithError
, Members
, send
, sendUsing

View File

@ -7,12 +7,8 @@
{-# OPTIONS_HADDOCK not-home #-}
module Polysemy.Internal.CustomErrors
( AmbiguousSend
, WhenStuck
( WhenStuck
, FirstOrder
, UnhandledEffect
, DefiningModule
, DefiningModuleForEffect
, type (<>)
, type (%)
) where
@ -25,18 +21,6 @@ import Polysemy.Internal.CustomErrors.Redefined
import Type.Errors hiding (IfStuck, WhenStuck, UnlessStuck)
------------------------------------------------------------------------------
-- | The module this effect was originally defined in. This type family is used
-- only for providing better error messages.
--
-- Calls to 'Polysemy.Internal.TH.Effect.makeSem' will automatically give
-- instances of 'DefiningModule'.
type family DefiningModule (t :: k) :: Symbol
type family DefiningModuleForEffect (e :: k) :: Symbol where
DefiningModuleForEffect (e a) = DefiningModuleForEffect e
DefiningModuleForEffect e = DefiningModule e
-- These are taken from type-errors-pretty because it's not in stackage for 9.0.1
-- See https://github.com/polysemy-research/polysemy/issues/401
type family ToErrorMessage (t :: k) :: ErrorMessage where
@ -77,51 +61,6 @@ type family ShowRQuoted (rstate :: EffectRowCtor) (r :: EffectRow) :: ErrorMessa
ShowRQuoted 'ConsR r = ShowTypeBracketed r
type AmbigousEffectMessage (rstate :: EffectRowCtor)
(r :: EffectRow)
(e :: k)
(t :: Effect)
(vs :: [Type])
= "Ambiguous use of effect '" <> e <> "'"
% "Possible fix:"
% " add (Member (" <> t <> ") " <> ShowRQuoted rstate r <> ") to the context of "
% " the type signature"
% "If you already have the constraint you want, instead"
% " add a type application to specify"
% " " <> PrettyPrintList vs <> " directly, or activate polysemy-plugin which"
% " can usually infer the type correctly."
type AmbiguousSend e r =
(IfStuck r
(AmbiguousSendError 'TyVarR r e)
(Pure (AmbiguousSendError (UnstuckRState r) r e)))
type family AmbiguousSendError rstate r e where
AmbiguousSendError rstate r (e a b c d f) =
TypeError (AmbigousEffectMessage rstate r e (e a b c d f) '[a, b c d f])
AmbiguousSendError rstate r (e a b c d) =
TypeError (AmbigousEffectMessage rstate r e (e a b c d) '[a, b c d])
AmbiguousSendError rstate r (e a b c) =
TypeError (AmbigousEffectMessage rstate r e (e a b c) '[a, b c])
AmbiguousSendError rstate r (e a b) =
TypeError (AmbigousEffectMessage rstate r e (e a b) '[a, b])
AmbiguousSendError rstate r (e a) =
TypeError (AmbigousEffectMessage rstate r e (e a) '[a])
AmbiguousSendError rstate r e =
TypeError
( "Could not deduce: (Member " <> e <> " " <> ShowRQuoted rstate r <> ") "
% "Fix:"
% " add (Member " <> e <> " " <> r <> ") to the context of"
% " the type signature"
)
data FirstOrderErrorFcf :: k -> Symbol -> Exp Constraint
type instance Eval (FirstOrderErrorFcf e fn) = $(te[t|
UnlessPhantom
@ -138,23 +77,5 @@ type instance Eval (FirstOrderErrorFcf e fn) = $(te[t|
type FirstOrder (e :: Effect) fn = UnlessStuck e (FirstOrderErrorFcf e fn)
------------------------------------------------------------------------------
-- | Unhandled effects
type UnhandledEffectMsg e
= "Unhandled effect '" <> e <> "'"
% "Probable fix:"
% " add an interpretation for '" <> e <> "'"
type CheckDocumentation e
= " If you are looking for inspiration, try consulting"
% " the documentation for module '" <> DefiningModuleForEffect e <> "'"
type family UnhandledEffect e where
UnhandledEffect e =
IfStuck (DefiningModule e)
(TypeError (UnhandledEffectMsg e))
(DoError (UnhandledEffectMsg e ':$$: CheckDocumentation e))
data DoError :: ErrorMessage -> Exp k
type instance Eval (DoError a) = TypeError a

View File

@ -33,7 +33,7 @@ import Language.Haskell.TH
import Language.Haskell.TH.Datatype
import Language.Haskell.TH.PprLib
import Polysemy.Internal (Sem, send)
import Polysemy.Internal.Union (MemberWithError)
import Polysemy.Internal.Union (Member)
#if __GLASGOW_HASKELL__ >= 804
import Prelude hiding ((<>))
@ -71,11 +71,11 @@ data ConLiftInfo = CLInfo
------------------------------------------------------------------------------
-- | Given an name of datatype or some of it's constructors/fields, return
-- datatype's name together with info about it's constructors.
getEffectMetadata :: Name -> Q (Name, [ConLiftInfo])
getEffectMetadata :: Name -> Q [ConLiftInfo]
getEffectMetadata type_name = do
dt_info <- reifyDatatype type_name
cl_infos <- traverse makeCLInfo $ constructorName <$> datatypeCons dt_info
pure (datatypeName dt_info, cl_infos)
pure cl_infos
------------------------------------------------------------------------------
@ -157,7 +157,7 @@ makeMemberConstraint r cli = makeMemberConstraint' r $ makeEffectType cli
-- | @'makeMemberConstraint'' r type@ will produce a @Member type r@
-- constraint.
makeMemberConstraint' :: Name -> Type -> Pred
makeMemberConstraint' r eff = classPred ''MemberWithError [eff, VarT r]
makeMemberConstraint' r eff = classPred ''Member [eff, VarT r]
------------------------------------------------------------------------------

View File

@ -32,7 +32,6 @@ module Polysemy.Internal.TH.Effect
import Control.Monad
import Language.Haskell.TH
import Language.Haskell.TH.Datatype
import Polysemy.Internal.CustomErrors (DefiningModule)
import Polysemy.Internal.TH.Common
@ -123,20 +122,11 @@ makeSem_ = genFreer False
genFreer :: Bool -> Name -> Q [Dec]
genFreer should_mk_sigs type_name = do
checkExtensions [ScopedTypeVariables, FlexibleContexts, DataKinds]
(dt_name, cl_infos) <- getEffectMetadata type_name
tyfams_on <- isExtEnabled TypeFamilies
def_mod_fi <- sequence [ tySynInstDCompat
''DefiningModule
Nothing
[pure $ ConT dt_name]
(LitT . StrTyLit . loc_module <$> location)
| tyfams_on
]
cl_infos <- getEffectMetadata type_name
decs <- traverse (genDec should_mk_sigs) cl_infos
let sigs = if should_mk_sigs then genSig <$> cl_infos else []
pure $ join $ def_mod_fi : sigs ++ decs
pure $ join $ sigs ++ decs
------------------------------------------------------------------------------

View File

@ -20,7 +20,6 @@ module Polysemy.Internal.Union
( Union (..)
, Weaving (..)
, Member
, MemberWithError
, weave
, hoist
-- * Building Unions
@ -58,10 +57,6 @@ import {-# SOURCE #-} Polysemy.Internal
import Polysemy.Internal.Sing (SList (SEnd, SCons))
import Unsafe.Coerce (unsafeCoerce)
#ifndef NO_ERROR_MESSAGES
import Polysemy.Internal.CustomErrors
#endif
------------------------------------------------------------------------------
-- | An extensible, type-safe union. The @r@ type parameter is a type-level
@ -137,42 +132,6 @@ hoist f' (Union w (Weaving e s nt f v)) =
Union w $ Weaving e s (f' . nt) f v
{-# INLINE hoist #-}
------------------------------------------------------------------------------
-- | A proof that the effect @e@ is available somewhere inside of the effect
-- stack @r@.
type Member e r = MemberNoError e r
------------------------------------------------------------------------------
-- | Like 'Member', but will produce an error message if the types are
-- ambiguous. This is the constraint used for actions generated by
-- 'Polysemy.makeSem'.
--
-- /Be careful with this./ Due to quirks of 'GHC.TypeLits.TypeError',
-- the custom error messages emitted by this can potentially override other,
-- more helpful error messages.
-- See the discussion in
-- <https://github.com/polysemy-research/polysemy/issues/227 Issue #227>.
--
-- @since 1.2.3.0
type MemberWithError e r =
( MemberNoError e r
#ifndef NO_ERROR_MESSAGES
-- NOTE: The plugin explicitly pattern matches on
-- `WhenStuck (LocateEffect _ r) _`, so if you change this, make sure to change
-- the corresponding implementation in
-- Polysemy.Plugin.Fundep.solveBogusError
, WhenStuck (LocateEffect e r) (AmbiguousSend e r)
#endif
)
type MemberNoError e r =
( Find e r
#ifndef NO_ERROR_MESSAGES
, LocateEffect e r ~ '()
#endif
)
------------------------------------------------------------------------------
-- | A proof that @e@ is an element of @r@.
--
@ -229,26 +188,15 @@ sameMember _ _ =
Nothing
------------------------------------------------------------------------------
-- | Used to detect ambiguous uses of effects. If @r@ isn't concrete,
-- and we haven't been given @'LocateEffect' e r ~ '()@ from a
-- @'Member' e r@ constraint, then @'LocateEffect' e r@ will get stuck.
type family LocateEffect (t :: k) (ts :: [k]) :: () where
#ifndef NO_ERROR_MESSAGES
LocateEffect t '[] = UnhandledEffect t
#endif
LocateEffect t (t ': ts) = '()
LocateEffect t (u ': ts) = LocateEffect t ts
class Find (t :: k) (r :: [k]) where
class Member (t :: Effect) (r :: EffectRow) where
membership' :: ElemOf t r
instance {-# OVERLAPPING #-} Find t (t ': z) where
instance {-# OVERLAPPING #-} Member t (t ': z) where
membership' = Here
{-# INLINE membership' #-}
instance Find t z => Find t (_1 ': z) where
membership' = There $ membership' @_ @t @z
instance Member t z => Member t (_1 ': z) where
membership' = There $ membership' @t @z
{-# INLINE membership' #-}
------------------------------------------------------------------------------

View File

@ -13,36 +13,6 @@ module TypeErrors where
-- >>> :m +Data.Maybe
--------------------------------------------------------------------------------
-- |
-- >>> :{
-- foo :: Sem r ()
-- foo = put ()
-- :}
-- ...
-- ... Ambiguous use of effect 'State'
-- ...
-- ... (Member (State ()) r) ...
-- ...
ambiguousMonoState = ()
--------------------------------------------------------------------------------
-- |
-- >>> :{
-- foo :: Sem r ()
-- foo = put 5
-- :}
-- ...
-- ... Ambiguous use of effect 'State'
-- ...
-- ... (Member (State s0) r) ...
-- ...
-- ... 's0' directly...
-- ...
ambiguousPolyState = ()
--------------------------------------------------------------------------------
-- |
-- >>> :{
@ -76,41 +46,6 @@ interpretBadFirstOrder = ()
tooFewArgumentsReinterpret = ()
--------------------------------------------------------------------------------
-- |
-- >>> :{
-- let reinterpretScrub :: Sem (Output Int ': m) a -> Sem (State Bool ': Trace ': m) a
-- reinterpretScrub = undefined
-- foo :: Sem '[Output Int] ()
-- foo = pure ()
-- foo' = reinterpretScrub foo
-- foo'' = runState True foo'
-- foo''' = traceToIO foo''
-- in runM foo'''
-- :}
-- ...
-- ... Unhandled effect 'Embed IO'
-- ...
-- ... Expected... Sem '[Embed m] (Bool, ())
-- ... Actual... Sem '[] (Bool, ())
-- ...
runningTooManyEffects = ()
--------------------------------------------------------------------------------
-- |
-- >>> :{
-- foo :: Sem (State Int ': r) ()
-- foo = put ()
-- :}
-- ...
-- ... Ambiguous use of effect 'State'
-- ...
-- ... (Member (State ()) (State Int : r)) ...
-- ...
ambiguousSendInConcreteR = ()
--------------------------------------------------------------------------------
-- |
-- >>> :{
@ -125,30 +60,3 @@ ambiguousSendInConcreteR = ()
-- ...
missingArgumentToRunResourceInIO = ()
--------------------------------------------------------------------------------
-- |
-- >>> :{
-- existsKV :: Member (State (Maybe Int)) r => Sem r Bool
-- existsKV = isJust get
-- :}
-- ...
-- ... Ambiguous use of effect 'State'
-- ...
--
-- NOTE: This is fixed by enabling the plugin!
missingFmap'PLUGIN = ()
--------------------------------------------------------------------------------
-- |
-- >>> :{
-- foo :: Sem '[State Int, Embed IO] ()
-- foo = output ()
-- :}
-- ...
-- ... Unhandled effect 'Output ()'
-- ...
-- ... add an interpretation for 'Output ()'
-- ...
missingEffectInStack'WRONG = ()