packages feed

monadic-bang 0.2.1.0 → 0.2.2.0

raw patch · 17 files changed

+1745/−1706 lines, 17 filesdep ~basedep ~ghcdep ~ghc-bootPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependency ranges changed: base, ghc, ghc-boot

API changes (from Hackage documentation)

- MonadicBang.Internal: [col] :: Loc -> Int
- MonadicBang.Internal: [invalid] :: InScope -> OccSet
- MonadicBang.Internal: [line] :: Loc -> Int
- MonadicBang.Internal: [valid] :: InScope -> OccSet
+ MonadicBang.Internal: MkOccs :: OccSet -> Occs
+ MonadicBang.Internal: [$sel:col:MkLoc] :: Loc -> Int
+ MonadicBang.Internal: [$sel:invalid:MkInScope] :: InScope -> Occs
+ MonadicBang.Internal: [$sel:line:MkLoc] :: Loc -> Int
+ MonadicBang.Internal: [$sel:valid:MkInScope] :: InScope -> Occs
+ MonadicBang.Internal: elemOccs :: OccName -> Occs -> Bool
+ MonadicBang.Internal: emptyOccs :: Occs
+ MonadicBang.Internal: extendOccs :: Occs -> OccName -> Occs
+ MonadicBang.Internal: instance GHC.Base.Monoid MonadicBang.Internal.Occs
+ MonadicBang.Internal: instance GHC.Base.Semigroup MonadicBang.Internal.Occs
+ MonadicBang.Internal: newtype Occs
+ MonadicBang.Internal: unitOccs :: OccName -> Occs
- MonadicBang.Internal: MkInScope :: OccSet -> OccSet -> InScope
+ MonadicBang.Internal: MkInScope :: Occs -> Occs -> InScope
- MonadicBang.Internal: addStmts :: forall (sig :: (Type -> Type) -> Type -> Type) m. Has (PsErrors :+: ((HoleFills :: (Type -> Type) -> Type -> Type) :+: ((Uniques :: (Type -> Type) -> Type -> Type) :+: (LocalVars :+: Reader DynFlags)))) sig m => Handler m [ExprLStmt GhcPs]
+ MonadicBang.Internal: addStmts :: forall sig m. Has (PsErrors :+: (HoleFills :+: (Uniques :+: (LocalVars :+: Reader DynFlags)))) sig m => Handler m [ExprLStmt GhcPs]
- MonadicBang.Internal: addValids :: OccSet -> InScope -> InScope
+ MonadicBang.Internal: addValids :: Occs -> InScope -> InScope
- MonadicBang.Internal: bangVar :: forall (sig :: (Type -> Type) -> Type -> Type) m. Has ((Uniques :: (Type -> Type) -> Type -> Type) :+: Reader DynFlags) sig m => LExpr -> Loc -> m RdrName
+ MonadicBang.Internal: bangVar :: Has (Uniques :+: Reader DynFlags) sig m => LExpr -> Loc -> m RdrName
- MonadicBang.Internal: class Typeable AstType a => Handle (a :: k) where {
+ MonadicBang.Internal: class Typeable (AstType a) => Handle a where {
- MonadicBang.Internal: class MonadTrans t => HandlingMonadTrans (t :: Type -> Type -> Type -> Type)
+ MonadicBang.Internal: class MonadTrans t => HandlingMonadTrans t
- MonadicBang.Internal: evac :: forall a (sig :: (Type -> Type) -> Type -> Type) m. (Has Fill sig m, Data a) => Handler m a
+ MonadicBang.Internal: evac :: forall a sig m. (Has Fill sig m, Data a) => Handler m a
- MonadicBang.Internal: evacPats :: forall a m (sig :: (Type -> Type) -> Type -> Type). (Has (Fill :+: State InScope) sig m, Data a) => Handler m a
+ MonadicBang.Internal: evacPats :: forall a m sig. (Has (Fill :+: State InScope) sig m, Data a) => Handler m a
- MonadicBang.Internal: fillHoles :: forall a (sig :: (Type -> Type) -> Type -> Type) m. (Data a, Has (PsErrors :+: (Reader Options :+: ((Uniques :: (Type -> Type) -> Type -> Type) :+: (LocalVars :+: Reader DynFlags)))) sig m) => Map Loc LExpr -> Handler m a
+ MonadicBang.Internal: fillHoles :: (Data a, Has (PsErrors :+: (Reader Options :+: (Uniques :+: (LocalVars :+: Reader DynFlags)))) sig m) => Map Loc LExpr -> Handler m a
- MonadicBang.Internal: handle :: forall {k} (a :: k) (sig :: (Type -> Type) -> Type -> Type) m. (Handle a, CanFail a ~ 'False, Has (Effects a) sig m) => Handler m (AstType a)
+ MonadicBang.Internal: handle :: forall a sig m. (Handle a, CanFail a ~ False) => Has (Effects a) sig m => Handler m (AstType a)
- MonadicBang.Internal: handle' :: forall (sig :: (Type -> Type) -> Type -> Type) m (m' :: Type -> Type). (Handle a, m ~ HandleFailure (CanFail a) m', Has (Effects a) sig m') => Handler m (AstType a)
+ MonadicBang.Internal: handle' :: forall sig m m'. (Handle a, m ~ HandleFailure (CanFail a) m') => Has (Effects a) sig m' => Handler m (AstType a)
- MonadicBang.Internal: ignore :: forall e (m :: Type -> Type) a. (Monad m, Typeable a, Typeable e) => Try m a
+ MonadicBang.Internal: ignore :: forall (e :: Type) m a. (Monad m, Typeable a, Typeable e) => Try m a
- MonadicBang.Internal: isInvalid :: forall (sig :: (Type -> Type) -> Type -> Type) m. Has (Reader InScope) sig m => OccName -> m Bool
+ MonadicBang.Internal: isInvalid :: Has (Reader InScope) sig m => OccName -> m Bool
- MonadicBang.Internal: locVar :: forall (sig :: (Type -> Type) -> Type -> Type) m. Has (Uniques :: (Type -> Type) -> Type -> Type) sig m => String -> SrcSpan -> Loc -> m RdrName
+ MonadicBang.Internal: locVar :: Has Uniques sig m => String -> SrcSpan -> Loc -> m RdrName
- MonadicBang.Internal: tellLocalVar :: forall (sig :: (Type -> Type) -> Type -> Type) m. Has (Writer OccSet) sig m => OccName -> m ()
+ MonadicBang.Internal: tellLocalVar :: Has (Writer Occs) sig m => OccName -> m ()
- MonadicBang.Internal: tellOne :: forall w (sig :: (Type -> Type) -> Type -> Type) m. Has (Writer (DList w)) sig m => w -> m ()
+ MonadicBang.Internal: tellOne :: Has (Writer (DList w)) sig m => w -> m ()
- MonadicBang.Internal: toMaybeT :: forall (m :: Type -> Type) a. (HandlingMonadTrans t, Monad m) => t m a -> MaybeT m a
+ MonadicBang.Internal: toMaybeT :: (HandlingMonadTrans t, Monad m) => t m a -> MaybeT m a
- MonadicBang.Internal: try :: forall {k} (e :: k) (sig :: (Type -> Type) -> Type -> Type) (m :: Type -> Type) a. (HandlingMonadTrans (HandleFailure (CanFail e)), Typeable a, Handle e, Monad m, Has (Effects e) sig m) => Try m a
+ MonadicBang.Internal: try :: forall e sig m a. (HandlingMonadTrans (HandleFailure (CanFail e)), Typeable a, Handle e, Monad m, Has (Effects e) sig m) => Try m a
- MonadicBang.Internal: tryEvac :: forall (m :: Type -> Type) a. Monad m => [Try m a] -> Try m a
+ MonadicBang.Internal: tryEvac :: Monad m => [Try m a] -> Try m a
- MonadicBang.Internal: type AstType (a :: k) = (r :: Type) | r -> k a;
+ MonadicBang.Internal: type AstType a = (r :: Type) | r -> a;
- MonadicBang.Internal: type CanFail (a :: k) :: Bool;
+ MonadicBang.Internal: type CanFail a :: Bool;
- MonadicBang.Internal: type Effects (a :: k) :: Type -> Type -> Type -> Type;
+ MonadicBang.Internal: type Effects a :: (Type -> Type) -> Type -> Type;
- MonadicBang.Internal: type Fill = PsErrors :+: Writer DList BindStmt :+: HoleFills :: Type -> Type -> Type -> Type :+: Uniques :: Type -> Type -> Type -> Type :+: LocalVars :+: Reader DynFlags
+ MonadicBang.Internal: type Fill = PsErrors :+: Writer (DList BindStmt) :+: HoleFills :+: Uniques :+: LocalVars :+: Reader DynFlags
- MonadicBang.Internal: type HoleFills = Offer Loc LExpr :: k -> Type -> Type
+ MonadicBang.Internal: type HoleFills = Offer Loc LExpr
- MonadicBang.Internal: type LocalVars = Reader InScope :+: Writer OccSet
+ MonadicBang.Internal: type LocalVars = Reader InScope :+: Writer Occs
- MonadicBang.Internal: type family HandleFailure (canFail :: Bool) = (t :: Type -> Type -> Type -> Type) | t -> canFail
+ MonadicBang.Internal: type family HandleFailure canFail = t | t -> canFail
- MonadicBang.Internal: usualTries :: forall (sig :: (Type -> Type) -> Type -> Type) (m :: Type -> Type) a. (Has Fill sig m, Data a) => [Try m a]
+ MonadicBang.Internal: usualTries :: (Has Fill sig m, Data a) => [Try m a]
- MonadicBang.Internal.Effect.Offer: OfferC :: StateC (Map k v) m a -> OfferC k v (m :: Type -> Type) a
+ MonadicBang.Internal.Effect.Offer: OfferC :: StateC (Map k v) m a -> OfferC k v m a
- MonadicBang.Internal.Effect.Offer: [Yoink] :: forall {k} k1 v (m :: k). k1 -> Offer k1 v m (Maybe v)
+ MonadicBang.Internal.Effect.Offer: [Yoink] :: k -> Offer k v m (Maybe v)
- MonadicBang.Internal.Effect.Offer: [getOfferState] :: OfferC k v (m :: Type -> Type) a -> StateC (Map k v) m a
+ MonadicBang.Internal.Effect.Offer: [getOfferState] :: OfferC k v m a -> StateC (Map k v) m a
- MonadicBang.Internal.Effect.Offer: data Offer k1 v (m :: k) a
+ MonadicBang.Internal.Effect.Offer: data Offer k v m a
- MonadicBang.Internal.Effect.Offer: newtype OfferC k v (m :: Type -> Type) a
+ MonadicBang.Internal.Effect.Offer: newtype OfferC k v m a
- MonadicBang.Internal.Effect.Offer: yoink :: forall k v (sig :: (Type -> Type) -> Type -> Type) m. Has (Offer k v :: (Type -> Type) -> Type -> Type) sig m => k -> m (Maybe v)
+ MonadicBang.Internal.Effect.Offer: yoink :: Has (Offer k v) sig m => k -> m (Maybe v)
- MonadicBang.Internal.Effect.Uniques: UniquesC :: StateC UniqSupply m a -> UniquesC (m :: Type -> Type) a
+ MonadicBang.Internal.Effect.Uniques: UniquesC :: StateC UniqSupply m a -> UniquesC m a
- MonadicBang.Internal.Effect.Uniques: [FreshUnique] :: forall {k} (m :: k). Uniques m Unique
+ MonadicBang.Internal.Effect.Uniques: [FreshUnique] :: Uniques m Unique
- MonadicBang.Internal.Effect.Uniques: [getUniquesState] :: UniquesC (m :: Type -> Type) a -> StateC UniqSupply m a
+ MonadicBang.Internal.Effect.Uniques: [getUniquesState] :: UniquesC m a -> StateC UniqSupply m a
- MonadicBang.Internal.Effect.Uniques: data Uniques (m :: k) a
+ MonadicBang.Internal.Effect.Uniques: data Uniques m a
- MonadicBang.Internal.Effect.Uniques: freshUnique :: forall (sig :: (Type -> Type) -> Type -> Type) m. Has (Uniques :: (Type -> Type) -> Type -> Type) sig m => m Unique
+ MonadicBang.Internal.Effect.Uniques: freshUnique :: Has Uniques sig m => m Unique
- MonadicBang.Internal.Effect.Uniques: newtype UniquesC (m :: Type -> Type) a
+ MonadicBang.Internal.Effect.Uniques: newtype UniquesC m a
- MonadicBang.Internal.Effect.Writer.Discard: DiscardC :: m a -> DiscardC (w :: k) (m :: k1 -> Type) (a :: k1)
+ MonadicBang.Internal.Effect.Writer.Discard: DiscardC :: m a -> DiscardC w m a
- MonadicBang.Internal.Effect.Writer.Discard: [evalDiscardC] :: DiscardC (w :: k) (m :: k1 -> Type) (a :: k1) -> m a
+ MonadicBang.Internal.Effect.Writer.Discard: [evalDiscardC] :: DiscardC w m a -> m a
- MonadicBang.Internal.Effect.Writer.Discard: evalWriter :: forall w (sig :: (Type -> Type) -> Type -> Type) m a. (Monoid w, Algebra sig m) => DiscardC w m a -> m a
+ MonadicBang.Internal.Effect.Writer.Discard: evalWriter :: (Monoid w, Algebra sig m) => DiscardC w m a -> m a
- MonadicBang.Internal.Effect.Writer.Discard: newtype DiscardC (w :: k) (m :: k1 -> Type) (a :: k1)
+ MonadicBang.Internal.Effect.Writer.Discard: newtype DiscardC w m a
- MonadicBang.Internal.Error: tellPsError :: forall (sig :: (Type -> Type) -> Type -> Type) m. Has PsErrors sig m => PsError -> SrcSpan -> m ()
+ MonadicBang.Internal.Error: tellPsError :: Has PsErrors sig m => PsError -> SrcSpan -> m ()
- MonadicBang.Internal.Error: type PsErrors = Writer Messages PsError
+ MonadicBang.Internal.Error: type PsErrors = Writer (Messages PsError)
- MonadicBang.Internal.Options: parseOptions :: forall (sig :: (Type -> Type) -> Type -> Type) m. Has (Throw ErrorCall) sig m => Located (HsModule GhcPs) -> [CommandLineOption] -> m Options
+ MonadicBang.Internal.Options: parseOptions :: Has (Throw ErrorCall) sig m => Located (HsModule GhcPs) -> [CommandLineOption] -> m Options
- MonadicBang.Internal.Utils: type Handler (m :: Type -> Type) a = a -> m a
+ MonadicBang.Internal.Utils: type Handler m a = a -> m a
- MonadicBang.Internal.Utils: type Try (m :: Type -> Type) a = Handler MaybeT m a
+ MonadicBang.Internal.Utils: type Try m a = Handler (MaybeT m) a

Files

CHANGELOG.md view
@@ -1,18 +1,22 @@-# Revision history for monadic-bang--## 0.2.0.0 -- 2023-10-16--* Added support for GHC 9.8-* Prefix internal modules with `Internal`--## 0.1.1.0 -- 2023-07-10--* Removed debug log message (thanks evincarofautumn!)-* Minor documentation fixes-* Added quotes to variables in error messages-* Added test for `-XQualifiedDo`-* Added support for GHC 9.6--## 0.1.0.0 -- 2023-01-07--* First version. Released on an unsuspecting world.+# Revision history for monadic-bang
+
+## 0.2.2.0 -- 2024-05-20
+
+* Added support for GHC 9.10
+
+## 0.2.1.0 -- 2023-10-16
+
+* Added support for GHC 9.8
+* Prefix internal modules with `Internal`
+
+## 0.1.1.0 -- 2023-07-10
+
+* Removed debug log message (thanks evincarofautumn!)
+* Minor documentation fixes
+* Added quotes to variables in error messages
+* Added test for `-XQualifiedDo`
+* Added support for GHC 9.6
+
+## 0.1.0.0 -- 2023-01-07
+
+* First version
README.md view
@@ -1,399 +1,399 @@-# Monadic Bang--[![Run Tests](https://github.com/JakobBruenker/monadic-bang/actions/workflows/haskell.yml/badge.svg?branch=main&event=push)](https://github.com/JakobBruenker/monadic-bang/actions/workflows/haskell.yml)--This is a GHC Parser plugin for GHC 9.4 and above, intended to make monadic code within `do`-blocks more concise and nicer to work with. Works with HLS.--This is heavily inspired by [Idris's !-notation](https://idris2.readthedocs.io/en/latest/tutorial/interfaces.html#notation), but with some [important differences](#comparison-with-idriss--notation).--## Contents--1. [Motivating Examples](#motivating-examples)-2. [Usage](#usage)-3. [Cute Things](#cute-things)-4. [Caveats](#caveats)-5. [Details](#details)-6. [Comparison with Idris's `!`-notation](#comparison-with-idriss--notation)--## Motivating Examples--Let's look at a few examples where Haskell syntax can be a bit annoying when it comes to monads - and what this plugin allows you to write instead:--When you use `Reader` or `State`, you will often have to use `<-` to bind fairly simple expressions:--```haskell-launchMissile :: StateT Int IO ()-launchMissile = do-  count <- get-  liftIO . putStrLn $ "Missile no. " <> show count <> " has been launched"-  modify' (+ 1)-```--```haskell-help :: Reader Config String-help = do-  manualLink <- asks (.links.manual)-  email <- asks (.contact.email)-  pure $-    "You can find help by going to " <> manualLink <>-    " or writing us at " <> email-```--With Monadic Bang, you can instead write-```haskell-launchMissile :: StateT Int IO ()-launchMissile = do-  liftIO . putStrLn $ "Missile no. " <> show !get <> " has been launched"-  modify' (+ 1)-```--```haskell-help :: Reader Config String-help = do-  pure $-    "You can find help by going to " <> (!ask).links.manual <>-    " or writing us at " <> (!ask).contact.email-```--With `IORefs`, `STRefs`, mutable arrays, and so on, you'll often have to write code that looks like this, having to use somewhat redundant variable names:--```haskell-addIORefs :: IORef Int -> IORef Int -> IO Int-addIORefs aRef bRef = do-  a <- readIORef aRef-  b <- readIORef bRef-  pure $ a + b-```--With Monadic Bang, you can write--```haskell-addIORefs :: IORef Int -> IORef Int -> IO Int-addIORefs a b = do pure $ !(readIORef a) + !(readIORef b)-```--Implicit parameter definitions have somewhat more limited syntax than regular definitions: You can't write something like `?foo <- action`.  -That lead me to have to write this in a Vulkan program:--```haskell-initQueues = do-  let getQueue = getDeviceQueue ?device-  graphicsQueue <- getQueue ?graphicsQueueFamily 0-  presentQueue  <- getQueue ?presentQueueFamily  0-  computeQueue  <- getQueue ?computeQueueFamily  1-  let ?graphicsQueue = graphicsQueue-      ?presentQueue  = presentQueue-      ?computeQueue  = computeQueue-  pure Dict-```--with Monadic Bang, I can write--```haskell-initQueues = do-  let getQueue = getDeviceQueue ?device-  let ?graphicsQueue = !(getQueue ?graphicsQueueFamily 0)-      ?presentQueue  = !(getQueue ?presentQueueFamily  0)-      ?computeQueue  = !(getQueue ?computeQueueFamily  1)-  pure Dict-```--Take this (slightly adapted) code used for the test suite of this very plugin:--```haskell-settings :: MonadIO m => m Settings-settings = ... -- some long function body--initialDynFlags :: MonadIO m => m DynFlags-initialDynFlags = do-  settings' <- settings-  dflags <- defaultDynFlags settings' llvmConfig-  pure $ dflags{generalFlags = addCompileFlags $ generalFlags dflags}-```--With this plugin, I can instead write--```haskell-settings :: MonadIO m => m Settings-settings = ... -- some long function body--initialDynFlags :: MonadIO m => m DynFlags-initialDynFlags = do-  dflags <- defaultDynFlags !settings llvmConfig-  pure $ dflags{generalFlags = addCompileFlags $ generalFlags dflags}-```--Or, to take some more code from this plugin's implementation--```haskell-do logger <- getLogger-   liftIO $ logMsg logger MCInfo (UnhelpfulSpan UnhelpfulNoLocationInfo) m-```-Why have `logger` *and* `getLogger` when you can instead write--```haskell-do liftIO $ logMsg !getLogger MCInfo (UnhelpfulSpan UnhelpfulNoLocationInfo) m-```--The pattern you might have noticed here is that this plugin is convenient-whenever you have a `do`-block with a `<-` that doesn't do pattern matching,-whose bound variable is only used once, and has a short right-hand side.  While-that might sound like a lot of qualifiers, it does occur fairly often in-practice.--## Usage--To use this plugin, you have to add `monadic-bang` to the `build-depends` stanza in your `.cabal` file. Then you can either add `-fplugin=MonadicBang` to the `ghc-options` stanza, or add--```haskell-{-# OPTIONS_GHC -fplugin=MonadicBang #-}-```--to the top of the files you want to use it in.--This should also allow HLS to pick up on the plugin, as long as you use HLS 1.9.0.0 or above.--The plugin supports a couple of options, which you can provide via invocations of `-fplugin-opt=MonadicBang:<option>`. The options are:--- `-ddump`: Print the altered AST-- `-preserve-errors`: Keep parse errors about `!` outside of `do` in their original form, rather than a more relevant explanation. This is mainly useful if another plugin expects those errors.--## Cute Things--### Idiom Brackets Alternative--In some cases where idiom brackets would be ideal, `!` can be a reasonable alternative. For example, compare these four options:--```haskell-1. liftA2 (&&) (readIORef useMetric) (readIORef useCelsius)-2. (&&) <$> readIORef useMetric <*> readIORef useCelsius-   -- hypothetical idiom brackets:-3. [| readIORef useMetric && readIORef useCelsius |]-   -- Monadic Bang:-4. do pure (!(readIORef useMetric) && !(readIORef useCelsius))-```--while `<$>` and `<*>` are probably better here for prefix functions, `!` plays nicer with infix operators.--If you have `-XApplicativeDo` enabled, this even works with `Applicative` instances.--### Nested `!`--`!` can easily be nested. E.g. you could have--```haskell-do putStrLn !(readFile (!getArgs !! 1))-```--For how this is desugared, see [Desugaring](#desugaring).--### Using `-XQualifiedDo`--`!` always has to be used inside a `do`-block, but it *can* be a qualified `do`-block. For example, if you use `-XLinearTypes`, you could write things like--```haskell-{-# LANGUAGE QualifiedDo, BlockArguments, OverloadedStrings #-}-import Prelude.Linear-import Control.Functor.Linear as Linear-import System.IO.Resource.Linear--main :: IO ()-main = run Linear.do-  Linear.pure !(move Linear.<$> hClose !(hPutStrLn !(openFile "tmp" WriteMode) "foo"))-```--which would be desugared as--```Haskell-main :: IO ()-main = run Linear.do-  a <- openFile "tmp" WriteMode-  b <- hPutStrLn a "foo"-  c <- move Linear.<$> hClose b-  Linear.pure c-```--### List comprehensions--List comprehensions are essentially just special `do`-blocks, so `!` can be used here as well (as well as in monad comprehensions). Example:--```haskell-[ x + ![1, 2, 3] | x <- [60, 70, ![800, 900]] ]-```-This would be equivalent to-```haskell-[ x + b | a <- [800, 900], x <- [60, 70, a], b <- [1, 2, 3]]-```--The reason `b <- ...` is at the end here instead of the beginning is that everything that appears to the left of the `|` in a list comprehension is essentially treated like the last statement of a `do`-block (+ `pure`).--### Get Rid of `<-`--In principle, every instance of `pattern <- action` in a `do`-block could be replaced by `let pattern = !action`. Should they? That's a separate question, though it could be a viable style.--The implicit parameter example in the first section is a valid use case of this.--### Monadic Variants--Oftentimes, some generic function exists, but then it turns out that a monadic variant of said function would be useful as well. For example, hoogle finds at least a dozen different packages offering `whenM`. With this plugin, you can instead write--```haskell-main = do-  when (null !getArgs) $ print usage-  ...-```--⚠️ NB: This works here since `when` only needs to evaluate its condition once. If you were to try to replace e.g. one of the forms of `whileM` in this manner, you would run into trouble since it's supposed to evaluate the condition again on each iteration.--## Caveats--There are a few disadvantages to using this that are worth mentioning:--- Since the plugin modifies the source code, the location info in error messages might look a bit strange, since it contains the desugared version. This shouldn't be an issue if you use HLS or another tool to highlight errors within your editor.-- HLint currently does not work with this plugin (HLint will show you a parse error if you try to use `!`.)-- If there are fatal parse errors in the source code, unfortunately each `!` will also be highlighted as a parse error. This is unavoidable at the moment, since the plugin can only intercept those messages if the module is otherwise successfully parsed.-- Plugins like this cannot be used inside of GHCi at this time (however, you can load modules that use it into GHCi).-- Arguably this makes `do`-desugaring slightly more confusing - e.g., compare the following:--  ```haskell-  do put 4-     put 5 >> print !get-  ``` -  -  ```haskell-  do put 4-     put 5-     print !get-  ```  -  -  With the usual desugaring rules, whether you use `>>` or a new line shouldn't make a difference, but here, the first snippet will print `4`, while the second snippet will print `5`.--  Because of this, the plugin is usually best used in situations where the order in which effects happen makes no difference.--## Details--While the above information should cover most use cases, there are some details that could sometimes be relevant--### Desugaring--The desugaring is essentially what one would expect from comparing the motivating examples with the versions using `!`.--To illustrate with a fairly extensive example:--```haskell-x = g do-  foo-  bar <- !a + !(!b ++ !c)-  baz <- case !d of-    (!f -> e) -> do !g e-```--is desugared into--```haskell-x = g do-  foo-  <!a> <- a-  <!b> <- b-  <!c> <- c-  <!(!b ++ !c)> <- <!b> ++ <!c>-  bar <- <!a> + <!(!b ++ !c)>-  <!d> <- d-  <!f> <- f-  baz <- case <!d> of-    (<!f> -> e) -> do-      <!g> <- g-      <!g> e-```--where `<!a>` etc. are simply special variable names.--So, broadly speaking, the order in which things are bound is top-to-bottom (statement-wise), inside-out, and left-to-right.--This can be important when the order of effects matters - though as mentioned above, if order *does* matter, `!` might not be the clearest way to express things.--`!` will only bubble up to the nearest `do`-block. To illustrate:--```haskell-x = do when nuclearStrikeDetected $ log !launchMissiles--y = do when nuclearStrikeDetected $ do log !launchMissiles-```--`x` will launch the missiles regardless of whether or not a strike has been detected. But it will only log the results in the case of detection.-`y` will only launch the missiles (and log the results) if a strike has been detected.--The desugaring:--```haskell-x = do-  <!launchMissiles> <- launchMissiles-  when nuclearStrikeDetected $ log <!launchMissiles>--y = do-  when nuclearStrikeDetected $ do-    <!launchMissiles> <- launchMissiles-    log <!launchMissiles>-```--The story for `case` and `if` expressions is similar, `!` in the individual branches will *all* be executed unless the branches have their own `do`-blocks.--### Variable scope--A variable can be used inside a `!` if-- it was bound outside the current `do`-block-- or it was bound before the statement the `!` is in-- or it is bound inside the `!`--In other words, this is legal:-```haskell-f x = do-  let a = a-  foo !(let b = b in x + a + b)-```-but this is not:-```haskell-c = do-  let a = a in foo !a-```-That's because this would be desugared as-```haskell-c = do-  <!a> <- a-  let a = a in foo <!a>-```-but `a` is not in scope in the second line.--### Where it can be used--It can be used in any expression that is somewhere inside a `do`-block. In particular, this includes for example `where`-blocks in `case`-expressions:--```haskell-main = do-  putStrLn case !getLine of-    "print args" -> prettyArgs "\n"-      where prettyArgs sep = intercalate sep !getArgs-    "greeting" -> "hello there!"-```--and view patterns--```haskell-do (extract !getSettings -> contents) <- readArchive-   print contents-```--## Comparison with Idris's `!`-notation--The main difference is that Idris will insert a `do` if there is none - e.g. this is legal in Idris:--```haskell-f : IO ()-f = putStrLn !getLine-```--but (assuming it's at top-level) wouldn't be with this plugin; you would have to write `f = do putStrLn !getLine` instead.--Some other differences:-- In Idris, `!`'d expressions cannot escape to outside of a lambda expression (it effectively inserts a new `do` at the beginning of the lambda body instead)-- The same difference applies to `let` bindings that define functions+# Monadic Bang
+
+[![Run Tests](https://github.com/JakobBruenker/monadic-bang/actions/workflows/haskell.yml/badge.svg?branch=main&event=push)](https://github.com/JakobBruenker/monadic-bang/actions/workflows/haskell.yml)
+
+This is a GHC Parser plugin for GHC 9.4 and above, intended to make monadic code within `do`-blocks more concise and nicer to work with. Works with HLS.
+
+This is heavily inspired by [Idris's !-notation](https://idris2.readthedocs.io/en/latest/tutorial/interfaces.html#notation), but with some [important differences](#comparison-with-idriss--notation).
+
+## Contents
+
+1. [Motivating Examples](#motivating-examples)
+2. [Usage](#usage)
+3. [Cute Things](#cute-things)
+4. [Caveats](#caveats)
+5. [Details](#details)
+6. [Comparison with Idris's `!`-notation](#comparison-with-idriss--notation)
+
+## Motivating Examples
+
+Let's look at a few examples where Haskell syntax can be a bit annoying when it comes to monads - and what this plugin allows you to write instead:
+
+When you use `Reader` or `State`, you will often have to use `<-` to bind fairly simple expressions:
+
+```haskell
+launchMissile :: StateT Int IO ()
+launchMissile = do
+  count <- get
+  liftIO . putStrLn $ "Missile no. " <> show count <> " has been launched"
+  modify' (+ 1)
+```
+
+```haskell
+help :: Reader Config String
+help = do
+  manualLink <- asks (.links.manual)
+  email <- asks (.contact.email)
+  pure $
+    "You can find help by going to " <> manualLink <>
+    " or writing us at " <> email
+```
+
+With Monadic Bang, you can instead write
+```haskell
+launchMissile :: StateT Int IO ()
+launchMissile = do
+  liftIO . putStrLn $ "Missile no. " <> show !get <> " has been launched"
+  modify' (+ 1)
+```
+
+```haskell
+help :: Reader Config String
+help = do
+  pure $
+    "You can find help by going to " <> (!ask).links.manual <>
+    " or writing us at " <> (!ask).contact.email
+```
+
+With `IORefs`, `STRefs`, mutable arrays, and so on, you'll often have to write code that looks like this, having to use somewhat redundant variable names:
+
+```haskell
+addIORefs :: IORef Int -> IORef Int -> IO Int
+addIORefs aRef bRef = do
+  a <- readIORef aRef
+  b <- readIORef bRef
+  pure $ a + b
+```
+
+With Monadic Bang, you can write
+
+```haskell
+addIORefs :: IORef Int -> IORef Int -> IO Int
+addIORefs a b = do pure $ !(readIORef a) + !(readIORef b)
+```
+
+Implicit parameter definitions have somewhat more limited syntax than regular definitions: You can't write something like `?foo <- action`.  
+That lead me to have to write this in a Vulkan program:
+
+```haskell
+initQueues = do
+  let getQueue = getDeviceQueue ?device
+  graphicsQueue <- getQueue ?graphicsQueueFamily 0
+  presentQueue  <- getQueue ?presentQueueFamily  0
+  computeQueue  <- getQueue ?computeQueueFamily  1
+  let ?graphicsQueue = graphicsQueue
+      ?presentQueue  = presentQueue
+      ?computeQueue  = computeQueue
+  pure Dict
+```
+
+with Monadic Bang, I can write
+
+```haskell
+initQueues = do
+  let getQueue = getDeviceQueue ?device
+  let ?graphicsQueue = !(getQueue ?graphicsQueueFamily 0)
+      ?presentQueue  = !(getQueue ?presentQueueFamily  0)
+      ?computeQueue  = !(getQueue ?computeQueueFamily  1)
+  pure Dict
+```
+
+Take this (slightly adapted) code used for the test suite of this very plugin:
+
+```haskell
+settings :: MonadIO m => m Settings
+settings = ... -- some long function body
+
+initialDynFlags :: MonadIO m => m DynFlags
+initialDynFlags = do
+  settings' <- settings
+  dflags <- defaultDynFlags settings' llvmConfig
+  pure $ dflags{generalFlags = addCompileFlags $ generalFlags dflags}
+```
+
+With this plugin, I can instead write
+
+```haskell
+settings :: MonadIO m => m Settings
+settings = ... -- some long function body
+
+initialDynFlags :: MonadIO m => m DynFlags
+initialDynFlags = do
+  dflags <- defaultDynFlags !settings llvmConfig
+  pure $ dflags{generalFlags = addCompileFlags $ generalFlags dflags}
+```
+
+Or, to take some more code from this plugin's implementation
+
+```haskell
+do logger <- getLogger
+   liftIO $ logMsg logger MCInfo (UnhelpfulSpan UnhelpfulNoLocationInfo) m
+```
+Why have `logger` *and* `getLogger` when you can instead write
+
+```haskell
+do liftIO $ logMsg !getLogger MCInfo (UnhelpfulSpan UnhelpfulNoLocationInfo) m
+```
+
+The pattern you might have noticed here is that this plugin is convenient
+whenever you have a `do`-block with a `<-` that doesn't do pattern matching,
+whose bound variable is only used once, and has a short right-hand side.  While
+that might sound like a lot of qualifiers, it does occur fairly often in
+practice.
+
+## Usage
+
+To use this plugin, you have to add `monadic-bang` to the `build-depends` stanza in your `.cabal` file. Then you can either add `-fplugin=MonadicBang` to the `ghc-options` stanza, or add
+
+```haskell
+{-# OPTIONS_GHC -fplugin=MonadicBang #-}
+```
+
+to the top of the files you want to use it in.
+
+This should also allow HLS to pick up on the plugin, as long as you use HLS 1.9.0.0 or above.
+
+The plugin supports a couple of options, which you can provide via invocations of `-fplugin-opt=MonadicBang:<option>`. The options are:
+
+- `-ddump`: Print the altered AST
+- `-preserve-errors`: Keep parse errors about `!` outside of `do` in their original form, rather than a more relevant explanation. This is mainly useful if another plugin expects those errors.
+
+## Cute Things
+
+### Idiom Brackets Alternative
+
+In some cases where idiom brackets would be ideal, `!` can be a reasonable alternative. For example, compare these four options:
+
+```haskell
+1. liftA2 (&&) (readIORef useMetric) (readIORef useCelsius)
+2. (&&) <$> readIORef useMetric <*> readIORef useCelsius
+   -- hypothetical idiom brackets:
+3. [| readIORef useMetric && readIORef useCelsius |]
+   -- Monadic Bang:
+4. do pure (!(readIORef useMetric) && !(readIORef useCelsius))
+```
+
+while `<$>` and `<*>` are probably better here for prefix functions, `!` plays nicer with infix operators.
+
+If you have `-XApplicativeDo` enabled, this even works with `Applicative` instances.
+
+### Nested `!`
+
+`!` can easily be nested. E.g. you could have
+
+```haskell
+do putStrLn !(readFile (!getArgs !! 1))
+```
+
+For how this is desugared, see [Desugaring](#desugaring).
+
+### Using `-XQualifiedDo`
+
+`!` always has to be used inside a `do`-block, but it *can* be a qualified `do`-block. For example, if you use `-XLinearTypes`, you could write things like
+
+```haskell
+{-# LANGUAGE QualifiedDo, BlockArguments, OverloadedStrings #-}
+import Prelude.Linear
+import Control.Functor.Linear as Linear
+import System.IO.Resource.Linear
+
+main :: IO ()
+main = run Linear.do
+  Linear.pure !(move Linear.<$> hClose !(hPutStrLn !(openFile "tmp" WriteMode) "foo"))
+```
+
+which would be desugared as
+
+```Haskell
+main :: IO ()
+main = run Linear.do
+  a <- openFile "tmp" WriteMode
+  b <- hPutStrLn a "foo"
+  c <- move Linear.<$> hClose b
+  Linear.pure c
+```
+
+### List comprehensions
+
+List comprehensions are essentially just special `do`-blocks, so `!` can be used here as well (as well as in monad comprehensions). Example:
+
+```haskell
+[ x + ![1, 2, 3] | x <- [60, 70, ![800, 900]] ]
+```
+This would be equivalent to
+```haskell
+[ x + b | a <- [800, 900], x <- [60, 70, a], b <- [1, 2, 3]]
+```
+
+The reason `b <- ...` is at the end here instead of the beginning is that everything that appears to the left of the `|` in a list comprehension is essentially treated like the last statement of a `do`-block (+ `pure`).
+
+### Get Rid of `<-`
+
+In principle, every instance of `pattern <- action` in a `do`-block could be replaced by `let pattern = !action`. Should they? That's a separate question, though it could be a viable style.
+
+The implicit parameter example in the first section is a valid use case of this.
+
+### Monadic Variants
+
+Oftentimes, some generic function exists, but then it turns out that a monadic variant of said function would be useful as well. For example, hoogle finds at least a dozen different packages offering `whenM`. With this plugin, you can instead write
+
+```haskell
+main = do
+  when (null !getArgs) $ print usage
+  ...
+```
+
+⚠️ NB: This works here since `when` only needs to evaluate its condition once. If you were to try to replace e.g. one of the forms of `whileM` in this manner, you would run into trouble since it's supposed to evaluate the condition again on each iteration.
+
+## Caveats
+
+There are a few disadvantages to using this that are worth mentioning:
+
+- Since the plugin modifies the source code, the location info in error messages might look a bit strange, since it contains the desugared version. This shouldn't be an issue if you use HLS or another tool to highlight errors within your editor.
+- HLint currently does not work with this plugin (HLint will show you a parse error if you try to use `!`.)
+- If there are fatal parse errors in the source code, unfortunately each `!` will also be highlighted as a parse error. This is unavoidable at the moment, since the plugin can only intercept those messages if the module is otherwise successfully parsed.
+- Plugins like this cannot be used inside of GHCi at this time (however, you can load modules that use it into GHCi).
+- Arguably this makes `do`-desugaring slightly more confusing - e.g., compare the following:
+
+  ```haskell
+  do put 4
+     put 5 >> print !get
+  ``` 
+  
+  ```haskell
+  do put 4
+     put 5
+     print !get
+  ```  
+  
+  With the usual desugaring rules, whether you use `>>` or a new line shouldn't make a difference, but here, the first snippet will print `4`, while the second snippet will print `5`.
+
+  Because of this, the plugin is usually best used in situations where the order in which effects happen makes no difference.
+
+## Details
+
+While the above information should cover most use cases, there are some details that could sometimes be relevant
+
+### Desugaring
+
+The desugaring is essentially what one would expect from comparing the motivating examples with the versions using `!`.
+
+To illustrate with a fairly extensive example:
+
+```haskell
+x = g do
+  foo
+  bar <- !a + !(!b ++ !c)
+  baz <- case !d of
+    (!f -> e) -> do !g e
+```
+
+is desugared into
+
+```haskell
+x = g do
+  foo
+  <!a> <- a
+  <!b> <- b
+  <!c> <- c
+  <!(!b ++ !c)> <- <!b> ++ <!c>
+  bar <- <!a> + <!(!b ++ !c)>
+  <!d> <- d
+  <!f> <- f
+  baz <- case <!d> of
+    (<!f> -> e) -> do
+      <!g> <- g
+      <!g> e
+```
+
+where `<!a>` etc. are simply special variable names.
+
+So, broadly speaking, the order in which things are bound is top-to-bottom (statement-wise), inside-out, and left-to-right.
+
+This can be important when the order of effects matters - though as mentioned above, if order *does* matter, `!` might not be the clearest way to express things.
+
+`!` will only bubble up to the nearest `do`-block. To illustrate:
+
+```haskell
+x = do when nuclearStrikeDetected $ log !launchMissiles
+
+y = do when nuclearStrikeDetected $ do log !launchMissiles
+```
+
+`x` will launch the missiles regardless of whether or not a strike has been detected. But it will only log the results in the case of detection.
+`y` will only launch the missiles (and log the results) if a strike has been detected.
+
+The desugaring:
+
+```haskell
+x = do
+  <!launchMissiles> <- launchMissiles
+  when nuclearStrikeDetected $ log <!launchMissiles>
+
+y = do
+  when nuclearStrikeDetected $ do
+    <!launchMissiles> <- launchMissiles
+    log <!launchMissiles>
+```
+
+The story for `case` and `if` expressions is similar, `!` in the individual branches will *all* be executed unless the branches have their own `do`-blocks.
+
+### Variable scope
+
+A variable can be used inside a `!` if
+- it was bound outside the current `do`-block
+- or it was bound before the statement the `!` is in
+- or it is bound inside the `!`
+
+In other words, this is legal:
+```haskell
+f x = do
+  let a = a
+  foo !(let b = b in x + a + b)
+```
+but this is not:
+```haskell
+c = do
+  let a = a in foo !a
+```
+That's because this would be desugared as
+```haskell
+c = do
+  <!a> <- a
+  let a = a in foo <!a>
+```
+but `a` is not in scope in the second line.
+
+### Where it can be used
+
+It can be used in any expression that is somewhere inside a `do`-block. In particular, this includes for example `where`-blocks in `case`-expressions:
+
+```haskell
+main = do
+  putStrLn case !getLine of
+    "print args" -> prettyArgs "\n"
+      where prettyArgs sep = intercalate sep !getArgs
+    "greeting" -> "hello there!"
+```
+
+and view patterns
+
+```haskell
+do (extract !getSettings -> contents) <- readArchive
+   print contents
+```
+
+## Comparison with Idris's `!`-notation
+
+The main difference is that Idris will insert a `do` if there is none - e.g. this is legal in Idris:
+
+```haskell
+f : IO ()
+f = putStrLn !getLine
+```
+
+but (assuming it's at top-level) wouldn't be with this plugin; you would have to write `f = do putStrLn !getLine` instead.
+
+Some other differences:
+- In Idris, `!`'d expressions cannot escape to outside of a lambda expression (it effectively inserts a new `do` at the beginning of the lambda body instead)
+- The same difference applies to `let` bindings that define functions
monadic-bang.cabal view
@@ -1,128 +1,129 @@-cabal-version:      2.4---- Initial package description 'monadic-bang.cabal' generated by--- 'cabal init'. For further documentation, see:---   http://haskell.org/cabal/users-guide/------ The name of the package.-name:               monadic-bang---- The package version.--- See the Haskell package versioning policy (PVP) for standards--- guiding when and how versions should be incremented.--- https://pvp.haskell.org--- PVP summary:      +-+------- breaking API changes---                   | | +----- non-breaking API additions---                   | | | +--- code changes with no API change-version:            0.2.1.0---- A short (one-line) description of the package.-synopsis:           GHC plugin to desugar ! into do-notation---- A longer description of the package.-description:        A plugin for GHC which takes expressions prefixed with a !-                    and effectively takes them out of their monadic context, by-                    creating bind statements in the do-block surrounding the-                    expression. Inspired by Idris's !-notation. For more-                    information, see README.md.---- URL for the project homepage or repository.-homepage:           https://github.com/JakobBruenker/monadic-bang---- A URL where users can report bugs.-bug-reports:        https://github.com/JakobBruenker/monadic-bang/issues---- The license under which the package is released.-license:            MIT---- The package author(s).-author:             Jakob Brünker---- An email address to which users can send suggestions, bug reports, and patches.-maintainer:         jakob.bruenker@gmail.com---- A copyright notice.--- copyright:-category:           Development--extra-doc-files:-    CHANGELOG.md-    README.md--tested-with:        GHC == 9.4.7-                    GHC == 9.6.3-                    GHC == 9.8.1--source-repository head-    type:           git-    location:       https://github.com/JakobBruenker/monadic-bang.git--library-    -- Modules exported by the library.-    exposed-modules:  MonadicBang-                      MonadicBang.Internal-                      MonadicBang.Internal.Effect.Offer-                      MonadicBang.Internal.Effect.Uniques-                      MonadicBang.Internal.Effect.Writer.Discard-                      MonadicBang.Internal.Options-                      MonadicBang.Internal.Utils-                      MonadicBang.Internal.Error--    -- Modules included in this library but not exported.-    -- other-modules:--    -- LANGUAGE extensions used by modules in this package.-    other-extensions: ScopedTypeVariables,-                      BlockArguments,-                      LambdaCase,-                      GADTs,-                      RecordWildCards,-                      OverloadedRecordDot,-                      NoFieldSelectors,-                      ViewPatterns,-                      StrictData,-                      PatternSynonyms--    -- Other library packages from which modules are imported.-    build-depends:    base >=4.17.0.0 && <4.20,-                      ghc >=9.4 && <9.9,-                      containers ^>=0.6.4.1 || ^>=0.7,-                      transformers >=0.5.6.2 && <0.7,-                      fused-effects ^>=1.1.1.2--    -- Directories containing source files.-    hs-source-dirs:   src--    -- Base language which the package is written in.-    default-language: GHC2021--    ghc-options:      -Wall -Wcompat--test-suite monadic-bang-test-    -- Base language which the package is written in.-    default-language: GHC2021--    -- The interface type and version of the test suite.-    type:             exitcode-stdio-1.0--    -- Directories containing source files.-    hs-source-dirs:   test--    -- The entrypoint to the test suite.-    main-is:          MonadicBang/Test.hs--    other-modules:    MonadicBang.Test.Utils.RunGhcParser-                      MonadicBang.Test.Utils.QualifiedDo-                      MonadicBang.Test.Utils-                      MonadicBang.Test.ShouldPass-                      MonadicBang.Test.ShouldFail--    -- Test dependencies.-    build-depends:    base,-                      ghc,-                      ghc-boot >=9.4 && <9.9,-                      ghc-paths ^>=0.1.0.12,-                      transformers,-                      monadic-bang--    ghc-options:      -Wall -Wcompat -plugin-package=monadic-bang+cabal-version:      2.4
+
+-- Initial package description 'monadic-bang.cabal' generated by
+-- 'cabal init'. For further documentation, see:
+--   http://haskell.org/cabal/users-guide/
+--
+-- The name of the package.
+name:               monadic-bang
+
+-- The package version.
+-- See the Haskell package versioning policy (PVP) for standards
+-- guiding when and how versions should be incremented.
+-- https://pvp.haskell.org
+-- PVP summary:      +-+------- breaking API changes
+--                   | | +----- non-breaking API additions
+--                   | | | +--- code changes with no API change
+version:            0.2.2.0
+
+-- A short (one-line) description of the package.
+synopsis:           GHC plugin to desugar ! into do-notation
+
+-- A longer description of the package.
+description:        A plugin for GHC which takes expressions prefixed with a !
+                    and effectively takes them out of their monadic context, by
+                    creating bind statements in the do-block surrounding the
+                    expression. Inspired by Idris's !-notation. For more
+                    information, see README.md.
+
+-- URL for the project homepage or repository.
+homepage:           https://github.com/JakobBruenker/monadic-bang
+
+-- A URL where users can report bugs.
+bug-reports:        https://github.com/JakobBruenker/monadic-bang/issues
+
+-- The license under which the package is released.
+license:            MIT
+
+-- The package author(s).
+author:             Jakob Brünker
+
+-- An email address to which users can send suggestions, bug reports, and patches.
+maintainer:         jakob.bruenker@gmail.com
+
+-- A copyright notice.
+-- copyright:
+category:           Development
+
+extra-doc-files:
+    CHANGELOG.md
+    README.md
+
+tested-with:        GHC == 9.4.7
+                    GHC == 9.6.3
+                    GHC == 9.8.1
+                    GHC == 9.10.1
+
+source-repository head
+    type:           git
+    location:       https://github.com/JakobBruenker/monadic-bang.git
+
+library
+    -- Modules exported by the library.
+    exposed-modules:  MonadicBang
+                      MonadicBang.Internal
+                      MonadicBang.Internal.Effect.Offer
+                      MonadicBang.Internal.Effect.Uniques
+                      MonadicBang.Internal.Effect.Writer.Discard
+                      MonadicBang.Internal.Options
+                      MonadicBang.Internal.Utils
+                      MonadicBang.Internal.Error
+
+    -- Modules included in this library but not exported.
+    -- other-modules:
+
+    -- LANGUAGE extensions used by modules in this package.
+    other-extensions: ScopedTypeVariables,
+                      BlockArguments,
+                      LambdaCase,
+                      GADTs,
+                      RecordWildCards,
+                      OverloadedRecordDot,
+                      NoFieldSelectors,
+                      ViewPatterns,
+                      StrictData,
+                      PatternSynonyms
+
+    -- Other library packages from which modules are imported.
+    build-depends:    base >=4.17.0.0 && <4.21,
+                      ghc >=9.4 && <9.11,
+                      containers ^>=0.6.4.1 || ^>=0.7,
+                      transformers >=0.5.6.2 && <0.7,
+                      fused-effects ^>=1.1.1.2
+
+    -- Directories containing source files.
+    hs-source-dirs:   src
+
+    -- Base language which the package is written in.
+    default-language: GHC2021
+
+    ghc-options:      -Wall -Wcompat
+
+test-suite monadic-bang-test
+    -- Base language which the package is written in.
+    default-language: GHC2021
+
+    -- The interface type and version of the test suite.
+    type:             exitcode-stdio-1.0
+
+    -- Directories containing source files.
+    hs-source-dirs:   test
+
+    -- The entrypoint to the test suite.
+    main-is:          MonadicBang/Test.hs
+
+    other-modules:    MonadicBang.Test.Utils.RunGhcParser
+                      MonadicBang.Test.Utils.QualifiedDo
+                      MonadicBang.Test.Utils
+                      MonadicBang.Test.ShouldPass
+                      MonadicBang.Test.ShouldFail
+
+    -- Test dependencies.
+    build-depends:    base,
+                      ghc,
+                      ghc-boot >=9.4 && <9.11,
+                      ghc-paths ^>=0.1.0.12,
+                      transformers,
+                      monadic-bang
+
+    ghc-options:      -Wall -Wcompat -plugin-package=monadic-bang
src/MonadicBang.hs view
@@ -1,13 +1,13 @@--- | GHC plugin to desugar ! into do-notation------ For more information, please refer to the README.-module MonadicBang (plugin) where--import GHC.Plugins-import MonadicBang.Internal--plugin :: Plugin-plugin = defaultPlugin-  { parsedResultAction = replaceBangs-  , pluginRecompile = purePlugin-  }+-- | GHC plugin to desugar ! into do-notation
+--
+-- For more information, please refer to the README.
+module MonadicBang (plugin) where
+
+import GHC.Plugins
+import MonadicBang.Internal
+
+plugin :: Plugin
+plugin = defaultPlugin
+  { parsedResultAction = replaceBangs
+  , pluginRecompile = purePlugin
+  }
src/MonadicBang/Internal.hs view
@@ -1,434 +1,466 @@-{-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE OverloadedRecordDot #-}-{-# LANGUAGE NoFieldSelectors #-}-{-# LANGUAGE ViewPatterns #-}-{-# LANGUAGE StrictData #-}-{-# LANGUAGE PatternSynonyms #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE TypeFamilyDependencies #-}-{-# LANGUAGE AllowAmbiguousTypes #-}-{-# LANGUAGE CPP #-}--module MonadicBang.Internal where--import Prelude hiding (log)-import Control.Applicative-import Control.Monad.Trans.Class-import Control.Monad.Trans.Maybe-import Control.Monad.Trans.Identity-import Control.Carrier.Reader-import Control.Carrier.Writer.Strict-import Control.Carrier.State.Strict-import Control.Carrier.Throw.Either-import Control.Carrier.Lift-import Control.Effect.Sum hiding (L)-import Control.Exception hiding (try, handle, Handler)-import Data.Data-import Data.Foldable-import Data.Functor-import Data.Map.Strict (Map)-import Data.Map.Strict qualified as M-import Data.Monoid-import GHC hiding (Type)-import GHC.Data.Bag-import GHC.Data.Maybe-import GHC.Parser.Errors.Types-import GHC.Plugins hiding (Type, Expr, empty, (<>), panic, try)-import GHC.Types.Error-import GHC.Utils.Monad (concatMapM, whenM)-import Text.Printf--import GHC.Utils.Logger--import MonadicBang.Internal.Effect.Offer-import MonadicBang.Internal.Effect.Uniques-import MonadicBang.Internal.Options-import MonadicBang.Internal.Utils-import MonadicBang.Internal.Error-import MonadicBang.Internal.Effect.Writer.Discard--import Data.Kind---- We don't care about which file things are from, because the entire AST comes--- from the same module-data Loc = MkLoc {line :: Int, col :: Int}-         deriving (Eq, Ord, Show)--type Expr = HsExpr GhcPs-type LExpr = LHsExpr GhcPs---- | To keep track of which local variables in scope may be used------ If local variables are defined within the same statement as a !, but outside--- of that !, they must not be used within this !, since their desugaring would--- make them escape their scope.-data InScope = MkInScope {valid :: OccSet , invalid :: OccSet}--instance Semigroup InScope where-  a <> b = MkInScope{valid = a.valid <> b.valid, invalid = a.invalid <> b.invalid}--instance Monoid InScope where-  mempty = noneInScope--noneInScope :: InScope-noneInScope = MkInScope emptyOccSet emptyOccSet--addValid :: OccName -> InScope -> InScope-addValid name inScope = inScope{valid = extendOccSet inScope.valid name}--addValids :: OccSet -> InScope -> InScope-addValids names inScope = inScope{valid = inScope.valid <> names}--invalidateVars :: InScope -> InScope-invalidateVars inScope = MkInScope{valid = emptyOccSet, invalid = inScope.valid <> inScope.invalid}--isInvalid :: Has (Reader InScope) sig m => OccName -> m Bool-isInvalid name = do-  inScope <- ask @InScope-  pure $ name `elemOccSet` inScope.invalid---- | Decrement column by one to get the location of a !-bangLoc :: Loc -> Loc-bangLoc loc = loc{col = loc.col - 1}---- | Decrement start by one column to get the location of a !-bangSpan :: SrcSpan -> SrcSpan-bangSpan sp = mkSrcSpan (bangSrcLoc $ srcSpanStart sp) (srcSpanEnd sp)---- | Decrement column by one to get the location of a !-bangSrcLoc :: SrcLoc -> SrcLoc-bangSrcLoc = \cases-  l@(UnhelpfulLoc _) -> l-  (RealSrcLoc srcLoc _) -> liftA3 mkSrcLoc srcLocFile srcLocLine (pred . srcLocCol) srcLoc---- | Used to extract the Loc of a located expression-pattern ExprLoc :: Loc -> Expr -> LExpr-pattern ExprLoc loc expr <- L (locA -> RealSrcSpan (spanToLoc -> loc) _) expr--spanToLoc :: RealSrcSpan -> Loc-spanToLoc = liftA2 MkLoc srcLocLine srcLocCol . realSrcSpanStart--replaceBangs :: [CommandLineOption] -> ModSummary -> Handler Hsc ParsedResult-replaceBangs cmdLineOpts _ (ParsedResult (HsParsedModule mod' files) msgs) = do-  options <- liftIO . (either throwIO pure =<<) . runThrow @ErrorCall $ parseOptions mod' cmdLineOpts-  dflags <- getDynFlags-  (newErrors, mod'') <--    runM .-    runUniquesIO 'p' .-    runWriter .-    runReader options .-    runReader noneInScope .-    evalWriter @OccSet .-    runReader dflags $-    fillHoles fills mod'-  log options.verbosity (ppr mod'')-  pure $ ParsedResult (HsParsedModule mod'' files) msgs{psErrors = oldErrors <> newErrors}-  where-    log = \cases-      Quiet _ -> pure ()-      DumpTransformed m -> do-        logger <- getLogger-        liftIO $ logMsg logger MCInfo (UnhelpfulSpan UnhelpfulNoLocationInfo) m--    -- Extract the errors we care about, throw the rest back in-    (mkMessages -> oldErrors, M.fromList . bagToList -> fills) =-      (partitionBagWith ?? msgs.psErrors.getMessages) \cases-        err | PsErrBangPatWithoutSpace lexpr@(ExprLoc (bangLoc -> loc) _) <- err.errMsgDiagnostic-            -> Right (loc, lexpr)-            | otherwise -> Left err-                  -type HandleFailure :: Bool -> (Type -> Type) -> (Type -> Type)-type family HandleFailure canFail = t | t -> canFail where-  HandleFailure True = MaybeT-  HandleFailure False = IdentityT--class MonadTrans t => HandlingMonadTrans t where-  toMaybeT :: Monad m => t m a -> MaybeT m a--instance HandlingMonadTrans IdentityT where-  toMaybeT = MaybeT . fmap Just . runIdentityT --instance HandlingMonadTrans MaybeT where-  toMaybeT = id--class Typeable (AstType a) => Handle a where-  type CanFail a :: Bool-  type AstType a = (r :: Type) | r -> a-  type Effects a :: (Type -> Type) -> Type -> Type-  handle' :: forall sig m m' . m ~ HandleFailure (CanFail a) m' => Has (Effects a) sig m' => Handler m (AstType a)--handle :: forall a sig m . (Handle a, CanFail a ~ False) => Has (Effects a) sig m => Handler m (AstType a)-handle = runIdentityT . handle'--try :: forall e sig m a .-       (HandlingMonadTrans (HandleFailure (CanFail e)), Typeable a, Handle e, Monad m, Has (Effects e) sig m) =>-       Try m a-try x = do-  Refl <- hoistMaybe $ eqT @a @(AstType e)-  toMaybeT $ handle' x--instance Handle GRHSs where-  type CanFail GRHSs = False-  type AstType GRHSs = GRHSs GhcPs LExpr-  type Effects GRHSs = Fill-  handle' grhss = do-    patVars <- ask @InScope-    grhssLocalBinds <- local (<> patVars) $ evac grhss.grhssLocalBinds-    grhssGRHSs <- evalState patVars $ evacPats grhss.grhssGRHSs-    pure grhss{grhssGRHSs, grhssLocalBinds}--instance Handle MatchGroup where-  type CanFail MatchGroup = False-  type AstType MatchGroup = MatchGroup GhcPs LExpr-  type Effects MatchGroup = Fill-  handle' mg = do-    mg_alts <- (traverse . traverse . traverse) handle mg.mg_alts-    pure mg{mg_alts}--instance Handle Match where-  type CanFail Match = False-  type AstType Match = Match GhcPs LExpr-  type Effects Match = Fill-  handle' match = do-    -- We use the State to keep track of the bindings that have been-    -- introduced in patterns to the left of the one we're currently looking-    -- at. Example:-    ---    -- > \a (Just [b, (+ b) -> d]) (foldr a b -> c) | Just f <- b, f == 24-    ---    -- the view pattern on `c` has access to the variables to the left of it. The same applies to `d`.-    -- `f == 24` additionally has access to variables defined in the guard to its left.-    (patVars, m_pats) <- ask @InScope >>= runState ?? evacPats match.m_pats-    m_grhss <- local (<> patVars) $ handle match.m_grhss-    pure match{m_pats, m_grhss}---- | We keep track of any local binds, to prevent the user from using them--- with ! in situations where they would be evacuated to a place where--- they're not in scope------ The plugin would still work without this, but might accept programs that--- shouldn't be accepted, with unexpected semantics. E.g:------ > do let s = pure "outer"--- >    let s = pure "inner" in putStrLn !s------ You might expect this to print `inner`, but it would actually print--- `outer`, since it would be desugared to------ > do let s = pure "outer"--- >    <!s> <- s--- >    let s = pure "inner" in print <!s>------ With this function, the plugin will instead throw an error saying that--- `s` cannot be used here.------ If the first `s` weren't defined, the user would, without this function,--- get an error saying that `s` is not in scope, at the call site. Here,--- we instead throw a more informative error.------ If only the first `s` were defined, i.e.------ > do let s = pure "outer"--- >    putStrLn !s------ it would be valid code.--instance Handle HsBindLR where-  type CanFail HsBindLR = True-  type AstType HsBindLR = HsBindLR GhcPs GhcPs-  type Effects HsBindLR = Fill-  handle' bind = case bind of-    FunBind{fun_id = occName . unLoc -> name, fun_matches = matches} -> do-      tellLocalVar name-      fun_matches <- local (addValid name) $ handle matches-      pure bind{fun_matches}-    PatBind{pat_lhs = lhs, pat_rhs = rhs} -> do-      (binds, pat_lhs) <- ask @InScope >>= flip runState (traverse evacPats lhs)-      pat_rhs <- local (<> binds) $ handle rhs-      pure bind{pat_lhs, pat_rhs}-    -- All VarBinds are introduced by the type checker, but we might as well handle them-    VarBind{var_id = occName -> name, var_rhs = expr} -> do-      tellLocalVar name-      var_rhs <- local (addValid name) $ evac expr-      pure bind{var_rhs}-    -- Pattern synonyms can never appear inside of do blocks, so we don't have-    -- to handle them specially-    PatSynBind{} -> empty--instance Handle Pat where-  type CanFail Pat = True-  type AstType Pat = Pat GhcPs-  type Effects Pat = Fill :+: State InScope-  handle' = \case-    VarPat xv name -> tellName name $> VarPat xv name-#if MIN_VERSION_ghc(9,6,0)-    AsPat xa name tok pat -> do-      tellName name-      AsPat xa name tok <$> traverse (liftMaybeT . evacPats) pat-#else-    AsPat xa name pat -> do-      tellName name-      AsPat xa name <$> traverse (liftMaybeT . evacPats) pat-#endif--    _ -> empty-    where-      tellName (occName . unLoc -> name) = do-        tellLocalVar name-        modify $ addValid name--instance Handle HsExpr where-  type CanFail HsExpr = True-  type AstType HsExpr = GenLocated SrcSpanAnnA Expr-  type Effects HsExpr = Fill-  handle' e@(L l _) = do-    ExprLoc loc expr <- pure e-    case expr of-      -- Replace holes resulting from `!`-      -- If no corresponding expression can be found in the Offer, we assume-      -- that it was a hole put there by the user and leave it unmodified-      HsUnboundVar _ _ -> yoink loc >>= maybe (pure e) \lexpr -> do-        -- all existing valid local variables now become invalid, since using-        -- them would make them escape their scope-        lexpr' <- local invalidateVars $ evac lexpr-        name <- bangVar lexpr' loc-        tellOne $ name :<- lexpr'-        pure . L l $ HsVar noExtField (noLocA name)-      HsVar _ (occName . unLoc -> name) -> do-        whenM (isInvalid name) do tellPsError (customError $ ErrOutOfScopeVariable name) l.locA-        pure e-      -- In HsDo, we can discard all in-scope variables in the context, since-      -- any !-desugaring we encounter cannot escape outside of this-      -- 'do'-block, and thus also not outside of the scope of those-      -- variables-      HsDo xd ctxt stmts -> L l . HsDo xd ctxt <$> local (const noneInScope) (traverse addStmts stmts)-      HsLet xl letTok binds inTok ex -> do-        (boundVars, binds') <- runWriter @OccSet $ evac binds-        fmap (L l . HsLet xl letTok binds' inTok) <$> liftMaybeT . local (addValids boundVars) $ evac ex--      _ -> empty--instance Handle StmtLR where-  type CanFail StmtLR = True-  type AstType StmtLR = StmtLR GhcPs GhcPs LExpr-  type Effects StmtLR = Fill-  handle' :: forall sig m m' . (m ~ MaybeT m', Has (Effects StmtLR) sig m') => Handler m (AstType StmtLR)-  handle' e = case e of--    RecStmt{recS_stmts} -> do-      recS_stmts' <- traverse addStmts recS_stmts-      pure e{recS_stmts = recS_stmts'}-    ParStmt xp stmtBlocks zipper bind -> do-      stmtsBlocks' <- traverse addParStmts stmtBlocks-      pure $ ParStmt xp stmtsBlocks' zipper bind-      where-        addParStmts :: Handler m (ParStmtBlock GhcPs GhcPs)-        addParStmts (ParStmtBlock xb stmts vars ret) = do-          stmts' <- addStmts stmts-          pure $ ParStmtBlock xb stmts' vars ret--    _ -> empty---- | Replace holes in an AST whenever an expression with the corresponding--- source span can be found in the given list.-fillHoles :: (Data a, Has (PsErrors :+: Reader Options :+: Uniques :+: LocalVars :+: Reader DynFlags) sig m) => Map Loc LExpr -> Handler m a-fillHoles fillers ast = do-  (remainingErrs, (fromDList -> binds :: [BindStmt], ast')) <- runOffer fillers . runWriter $ evac ast-  MkOptions{preserveErrors} <- ask-  for_ binds \bind -> tellPsError (psError (bindStmtExpr bind) preserveErrors) (bangSpan $ bindStmtSpan bind)-  dflags <- ask-  pure if null remainingErrs-    then ast'-    else panic $ unlines $ "Found extraneous bangs:" : (showPpr dflags <$> toList remainingErrs)-  where-    psError expr = \cases-      Preserve      -> PsErrBangPatWithoutSpace expr-      Don'tPreserve -> customError ErrBangOutsideOfDo--evac :: forall a sig m . (Has Fill sig m, Data a) => Handler m a--- This recurses over all nodes in the AST, except for nodes for which--- one of the `try` functions returns `Just <something>`.-evac e = maybe (gmapM evac e) pure =<< runMaybeT (tryEvac usualTries e)--tryEvac :: Monad m => [Try m a] -> Try m a-tryEvac tries = asum . (tries ??)--usualTries :: (Has Fill sig m, Data a) => [Try m a]-usualTries =-  [ try @HsExpr, try @HsBindLR, try @MatchGroup, try @StmtLR-  , ignore @RdrName, ignore @OccName, ignore @RealSrcSpan, ignore @EpAnnComments-  ]---- As a minor performance optimization, we don't recurse over the AST if the node--- is a type that we know will never contain an expression-ignore :: forall (e :: Type) m a . (Monad m, Typeable a, Typeable e) => Try m a-ignore e = do-  Refl <- hoistMaybe $ eqT @e @a-  pure e---- | evacuate !s in pattern and collect all the names it binds-evacPats :: forall a m sig . (Has (Fill :+: State InScope) sig m, Data a) => Handler m a-evacPats e = do-  currentState <- get @InScope-  maybe (gmapM evacPats e) pure =<< runMaybeT (tryEvac ((local (<> currentState) .) <$> (try @Pat : usualTries)) e)---- | Find all !s in the given statements and combine the resulting bind--- statements into lists, with the original statements being the last one--- in each list - then concatenate these lists-addStmts :: forall sig m . Has (PsErrors :+: HoleFills :+: Uniques :+: LocalVars :+: Reader DynFlags) sig m => Handler m [ExprLStmt GhcPs]-addStmts = concatMapM \lstmt -> do-  (fromDList -> stmts, lstmt') <- runWriter $ evac lstmt-  pure $ map fromBindStmt stmts ++ [lstmt']--type HoleFills = Offer Loc LExpr--- | We keep track of variables that are bound in lambdas, cases, etc., since--- these are variables that will not be accessible in the surrounding--- 'do'-block, and must therefore not be used.--- The Reader is used to find out what local variables are in scope, the Writer--- is used to inform callers which local variables have been bound.-type LocalVars = Reader InScope :+: Writer OccSet--type Fill = PsErrors :+: Writer (DList BindStmt) :+: HoleFills :+: Uniques :+: LocalVars :+: Reader DynFlags--data BindStmt = RdrName :<- LExpr--bindStmtExpr :: BindStmt -> LExpr-bindStmtExpr (_ :<- expr) = expr--bindStmtSpan :: BindStmt -> SrcSpan-bindStmtSpan = (.locA) . \(_ :<- L l _) -> l--fromBindStmt :: BindStmt -> ExprLStmt GhcPs-fromBindStmt = noLocA . \cases-  (var :<- lexpr) -> BindStmt EpAnnNotUsed varPat lexpr-    where-      varPat = noLocA . VarPat noExtField $ noLocA var---- | Use the !'d expression if it's short enough, or else abbreviate with `...`--- We don't need to worry about shadowing other !'d expressions:--- - For the user, we add line and column numbers to the name--- - For the compiler, we use a unique instead of the name-bangVar :: Has (Uniques :+: Reader DynFlags) sig m => LExpr -> Loc -> m RdrName-bangVar (L spn expr) loc = do-  dflags <- ask-  let name = '!' : case lines (showPpr dflags expr) of-        (str:rest) | null rest && length str < 20 -> str-                   | otherwise                    -> take 16 str ++ "..."-        _                                         -> "<empty expression>"-  locVar name spn.locA loc--locVar :: Has Uniques sig m => String -> SrcSpan -> Loc -> m RdrName-locVar str spn loc = do-  let occ = mkVarOcc $ printf "<%s:%d:%d>" str loc.line loc.col-  unique <- freshUnique-  pure . nameRdrName $ mkInternalName unique occ spn--tellOne :: Has (Writer (DList w)) sig m => w -> m ()-tellOne x = tell $ Endo (x:)--tellLocalVar :: Has (Writer OccSet) sig m => OccName -> m ()-tellLocalVar = tell . unitOccSet+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE NoFieldSelectors #-}
+{-# LANGUAGE ViewPatterns #-}
+{-# LANGUAGE StrictData #-}
+{-# LANGUAGE PatternSynonyms #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE DataKinds #-}
+{-# LANGUAGE TypeFamilyDependencies #-}
+{-# LANGUAGE AllowAmbiguousTypes #-}
+{-# LANGUAGE CPP #-}
+
+module MonadicBang.Internal where
+
+import Prelude hiding (log)
+import Control.Applicative
+import Control.Monad.Trans.Class
+import Control.Monad.Trans.Maybe
+import Control.Monad.Trans.Identity
+import Control.Carrier.Reader
+import Control.Carrier.Writer.Strict
+import Control.Carrier.State.Strict
+import Control.Carrier.Throw.Either
+import Control.Carrier.Lift
+import Control.Effect.Sum hiding (L)
+import Control.Exception hiding (try, handle, Handler)
+import Data.Data
+import Data.Foldable
+import Data.Functor
+import Data.Map.Strict (Map)
+import Data.Map.Strict qualified as M
+import Data.Monoid
+import GHC hiding (Type)
+import GHC.Data.Bag
+import GHC.Data.Maybe
+import GHC.Parser.Errors.Types
+import GHC.Plugins hiding (Type, Expr, empty, (<>), panic, try)
+import GHC.Types.Error
+import GHC.Utils.Monad (concatMapM, whenM)
+import Text.Printf
+
+import GHC.Utils.Logger
+
+import MonadicBang.Internal.Effect.Offer
+import MonadicBang.Internal.Effect.Uniques
+import MonadicBang.Internal.Options
+import MonadicBang.Internal.Utils
+import MonadicBang.Internal.Error
+import MonadicBang.Internal.Effect.Writer.Discard
+
+import Data.Kind
+import Data.Coerce
+
+-- We don't care about which file things are from, because the entire AST comes
+-- from the same module
+data Loc = MkLoc {line :: Int, col :: Int}
+         deriving (Eq, Ord, Show)
+
+type Expr = HsExpr GhcPs
+type LExpr = LHsExpr GhcPs
+
+-- | OccSet newtype that allows us to define an orphan Monoid instance
+newtype Occs = MkOccs OccSet
+
+instance Semigroup Occs where
+  (<>) = coerce unionOccSets
+
+instance Monoid Occs where
+  mempty = emptyOccs
+
+emptyOccs :: Occs
+emptyOccs = coerce emptyOccSet
+
+extendOccs :: Occs -> OccName -> Occs
+extendOccs = coerce extendOccSet
+
+elemOccs :: OccName -> Occs -> Bool
+elemOccs = coerce elemOccSet
+
+unitOccs :: OccName -> Occs
+unitOccs = coerce unitOccSet
+
+-- | To keep track of which local variables in scope may be used
+--
+-- If local variables are defined within the same statement as a !, but outside
+-- of that !, they must not be used within this !, since their desugaring would
+-- make them escape their scope.
+data InScope = MkInScope {valid :: Occs , invalid :: Occs}
+
+instance Semigroup InScope where
+  a <> b = MkInScope{valid = a.valid <> b.valid, invalid = a.invalid <> b.invalid}
+
+instance Monoid InScope where
+  mempty = noneInScope
+
+noneInScope :: InScope
+noneInScope = MkInScope emptyOccs emptyOccs
+
+addValid :: OccName -> InScope -> InScope
+addValid name inScope = inScope{valid = extendOccs inScope.valid name}
+
+addValids :: Occs -> InScope -> InScope
+addValids names inScope = inScope{valid = inScope.valid <> names}
+
+invalidateVars :: InScope -> InScope
+invalidateVars inScope = MkInScope{valid = emptyOccs, invalid = inScope.valid <> inScope.invalid}
+
+isInvalid :: Has (Reader InScope) sig m => OccName -> m Bool
+isInvalid name = do
+  inScope <- ask @InScope
+  pure $ name `elemOccs` inScope.invalid
+
+-- | Decrement column by one to get the location of a !
+bangLoc :: Loc -> Loc
+bangLoc loc = loc{col = loc.col - 1}
+
+-- | Decrement start by one column to get the location of a !
+bangSpan :: SrcSpan -> SrcSpan
+bangSpan sp = mkSrcSpan (bangSrcLoc $ srcSpanStart sp) (srcSpanEnd sp)
+
+-- | Decrement column by one to get the location of a !
+bangSrcLoc :: SrcLoc -> SrcLoc
+bangSrcLoc = \cases
+  l@(UnhelpfulLoc _) -> l
+  (RealSrcLoc srcLoc _) -> liftA3 mkSrcLoc srcLocFile srcLocLine (pred . srcLocCol) srcLoc
+
+-- | Used to extract the Loc of a located expression
+pattern ExprLoc :: Loc -> Expr -> LExpr
+pattern ExprLoc loc expr <- L (locA -> RealSrcSpan (spanToLoc -> loc) _) expr
+
+spanToLoc :: RealSrcSpan -> Loc
+spanToLoc = liftA2 MkLoc srcLocLine srcLocCol . realSrcSpanStart
+
+replaceBangs :: [CommandLineOption] -> ModSummary -> Handler Hsc ParsedResult
+replaceBangs cmdLineOpts _ (ParsedResult (HsParsedModule mod' files) msgs) = do
+  options <- liftIO . (either throwIO pure =<<) . runThrow @ErrorCall $ parseOptions mod' cmdLineOpts
+  dflags <- getDynFlags
+  (newErrors, mod'') <-
+    runM .
+    runUniquesIO 'p' .
+    runWriter .
+    runReader options .
+    runReader noneInScope .
+    evalWriter @Occs .
+    runReader dflags $
+    fillHoles fills mod'
+  log options.verbosity (ppr mod'')
+  pure $ ParsedResult (HsParsedModule mod'' files) msgs{psErrors = oldErrors <> newErrors}
+  where
+    log = \cases
+      Quiet _ -> pure ()
+      DumpTransformed m -> do
+        logger <- getLogger
+        liftIO $ logMsg logger MCInfo (UnhelpfulSpan UnhelpfulNoLocationInfo) m
+
+    -- Extract the errors we care about, throw the rest back in
+    (mkMessages -> oldErrors, M.fromList . bagToList -> fills) =
+      (partitionBagWith ?? msgs.psErrors.getMessages) \cases
+        err | PsErrBangPatWithoutSpace lexpr@(ExprLoc (bangLoc -> loc) _) <- err.errMsgDiagnostic
+            -> Right (loc, lexpr)
+            | otherwise -> Left err
+                  
+type HandleFailure :: Bool -> (Type -> Type) -> (Type -> Type)
+type family HandleFailure canFail = t | t -> canFail where
+  HandleFailure True = MaybeT
+  HandleFailure False = IdentityT
+
+class MonadTrans t => HandlingMonadTrans t where
+  toMaybeT :: Monad m => t m a -> MaybeT m a
+
+instance HandlingMonadTrans IdentityT where
+  toMaybeT = MaybeT . fmap Just . runIdentityT 
+
+instance HandlingMonadTrans MaybeT where
+  toMaybeT = id
+
+class Typeable (AstType a) => Handle a where
+  type CanFail a :: Bool
+  type AstType a = (r :: Type) | r -> a
+  type Effects a :: (Type -> Type) -> Type -> Type
+  handle' :: forall sig m m' . m ~ HandleFailure (CanFail a) m' => Has (Effects a) sig m' => Handler m (AstType a)
+
+handle :: forall a sig m . (Handle a, CanFail a ~ False) => Has (Effects a) sig m => Handler m (AstType a)
+handle = runIdentityT . handle'
+
+try :: forall e sig m a .
+       (HandlingMonadTrans (HandleFailure (CanFail e)), Typeable a, Handle e, Monad m, Has (Effects e) sig m) =>
+       Try m a
+try x = do
+  Refl <- hoistMaybe $ eqT @a @(AstType e)
+  toMaybeT $ handle' x
+
+instance Handle GRHSs where
+  type CanFail GRHSs = False
+  type AstType GRHSs = GRHSs GhcPs LExpr
+  type Effects GRHSs = Fill
+  handle' grhss = do
+    patVars <- ask @InScope
+    grhssLocalBinds <- local (<> patVars) $ evac grhss.grhssLocalBinds
+    grhssGRHSs <- evalState patVars $ evacPats grhss.grhssGRHSs
+    pure grhss{grhssGRHSs, grhssLocalBinds}
+
+instance Handle MatchGroup where
+  type CanFail MatchGroup = False
+  type AstType MatchGroup = MatchGroup GhcPs LExpr
+  type Effects MatchGroup = Fill
+  handle' mg = do
+    mg_alts <- (traverse . traverse . traverse) handle mg.mg_alts
+    pure mg{mg_alts}
+
+instance Handle Match where
+  type CanFail Match = False
+  type AstType Match = Match GhcPs LExpr
+  type Effects Match = Fill
+  handle' match = do
+    -- We use the State to keep track of the bindings that have been
+    -- introduced in patterns to the left of the one we're currently looking
+    -- at. Example:
+    --
+    -- > \a (Just [b, (+ b) -> d]) (foldr a b -> c) | Just f <- b, f == 24
+    --
+    -- the view pattern on `c` has access to the variables to the left of it. The same applies to `d`.
+    -- `f == 24` additionally has access to variables defined in the guard to its left.
+    (patVars, m_pats) <- ask @InScope >>= runState ?? evacPats match.m_pats
+    m_grhss <- local (<> patVars) $ handle match.m_grhss
+    pure match{m_pats, m_grhss}
+
+-- | We keep track of any local binds, to prevent the user from using them
+-- with ! in situations where they would be evacuated to a place where
+-- they're not in scope
+--
+-- The plugin would still work without this, but might accept programs that
+-- shouldn't be accepted, with unexpected semantics. E.g:
+--
+-- > do let s = pure "outer"
+-- >    let s = pure "inner" in putStrLn !s
+--
+-- You might expect this to print `inner`, but it would actually print
+-- `outer`, since it would be desugared to
+--
+-- > do let s = pure "outer"
+-- >    <!s> <- s
+-- >    let s = pure "inner" in print <!s>
+--
+-- With this function, the plugin will instead throw an error saying that
+-- `s` cannot be used here.
+--
+-- If the first `s` weren't defined, the user would, without this function,
+-- get an error saying that `s` is not in scope, at the call site. Here,
+-- we instead throw a more informative error.
+--
+-- If only the first `s` were defined, i.e.
+--
+-- > do let s = pure "outer"
+-- >    putStrLn !s
+--
+-- it would be valid code.
+
+instance Handle HsBindLR where
+  type CanFail HsBindLR = True
+  type AstType HsBindLR = HsBindLR GhcPs GhcPs
+  type Effects HsBindLR = Fill
+  handle' bind = case bind of
+    FunBind{fun_id = occName . unLoc -> name, fun_matches = matches} -> do
+      tellLocalVar name
+      fun_matches <- local (addValid name) $ handle matches
+      pure bind{fun_matches}
+    PatBind{pat_lhs = lhs, pat_rhs = rhs} -> do
+      (binds, pat_lhs) <- ask @InScope >>= flip runState (traverse evacPats lhs)
+      pat_rhs <- local (<> binds) $ handle rhs
+      pure bind{pat_lhs, pat_rhs}
+    -- All VarBinds are introduced by the type checker, but we might as well handle them
+    VarBind{var_id = occName -> name, var_rhs = expr} -> do
+      tellLocalVar name
+      var_rhs <- local (addValid name) $ evac expr
+      pure bind{var_rhs}
+    -- Pattern synonyms can never appear inside of do blocks, so we don't have
+    -- to handle them specially
+    PatSynBind{} -> empty
+
+instance Handle Pat where
+  type CanFail Pat = True
+  type AstType Pat = Pat GhcPs
+  type Effects Pat = Fill :+: State InScope
+  handle' = \case
+    VarPat xv name -> tellName name $> VarPat xv name
+#if MIN_VERSION_ghc(9,10,0)
+    AsPat xa name pat -> do
+      tellName name
+      AsPat xa name <$> traverse (liftMaybeT . evacPats) pat
+#elif MIN_VERSION_ghc(9,6,0)
+    AsPat xa name tok pat -> do
+      tellName name
+      AsPat xa name tok <$> traverse (liftMaybeT . evacPats) pat
+#else
+    AsPat xa name pat -> do
+      tellName name
+      AsPat xa name <$> traverse (liftMaybeT . evacPats) pat
+#endif
+
+    _ -> empty
+    where
+      tellName (occName . unLoc -> name) = do
+        tellLocalVar name
+        modify $ addValid name
+
+instance Handle HsExpr where
+  type CanFail HsExpr = True
+  type AstType HsExpr = GenLocated SrcSpanAnnA Expr
+  type Effects HsExpr = Fill
+  handle' e@(L l _) = do
+    ExprLoc loc expr <- pure e
+    case expr of
+      -- Replace holes resulting from `!`
+      -- If no corresponding expression can be found in the Offer, we assume
+      -- that it was a hole put there by the user and leave it unmodified
+      HsUnboundVar _ _ -> yoink loc >>= maybe (pure e) \lexpr -> do
+        -- all existing valid local variables now become invalid, since using
+        -- them would make them escape their scope
+        lexpr' <- local invalidateVars $ evac lexpr
+        name <- bangVar lexpr' loc
+        tellOne $ name :<- lexpr'
+        pure . L l $ HsVar noExtField (noLocA name)
+      HsVar _ (occName . unLoc -> name) -> do
+        whenM (isInvalid name) do tellPsError (customError $ ErrOutOfScopeVariable name) (locA l)
+        pure e
+      -- In HsDo, we can discard all in-scope variables in the context, since
+      -- any !-desugaring we encounter cannot escape outside of this
+      -- 'do'-block, and thus also not outside of the scope of those
+      -- variables
+      HsDo xd ctxt stmts -> L l . HsDo xd ctxt <$> local (const noneInScope) (traverse addStmts stmts)
+#if MIN_VERSION_ghc(9,10,0)
+      HsLet xl binds ex -> do
+        (boundVars, binds') <- runWriter @Occs $ evac binds
+        fmap (L l . HsLet xl binds') <$> liftMaybeT . local (addValids boundVars) $ evac ex
+#else
+      HsLet xl letTok binds inTok ex -> do
+        (boundVars, binds') <- runWriter @Occs $ evac binds
+        fmap (L l . HsLet xl letTok binds' inTok) <$> liftMaybeT . local (addValids boundVars) $ evac ex
+#endif
+
+      _ -> empty
+
+instance Handle StmtLR where
+  type CanFail StmtLR = True
+  type AstType StmtLR = StmtLR GhcPs GhcPs LExpr
+  type Effects StmtLR = Fill
+  handle' :: forall sig m m' . (m ~ MaybeT m', Has (Effects StmtLR) sig m') => Handler m (AstType StmtLR)
+  handle' e = case e of
+
+    RecStmt{recS_stmts} -> do
+      recS_stmts' <- traverse addStmts recS_stmts
+      pure e{recS_stmts = recS_stmts'}
+    ParStmt xp stmtBlocks zipper bind -> do
+      stmtsBlocks' <- traverse addParStmts stmtBlocks
+      pure $ ParStmt xp stmtsBlocks' zipper bind
+      where
+        addParStmts :: Handler m (ParStmtBlock GhcPs GhcPs)
+        addParStmts (ParStmtBlock xb stmts vars ret) = do
+          stmts' <- addStmts stmts
+          pure $ ParStmtBlock xb stmts' vars ret
+
+    _ -> empty
+
+-- | Replace holes in an AST whenever an expression with the corresponding
+-- source span can be found in the given list.
+fillHoles :: (Data a, Has (PsErrors :+: Reader Options :+: Uniques :+: LocalVars :+: Reader DynFlags) sig m) => Map Loc LExpr -> Handler m a
+fillHoles fillers ast = do
+  (remainingErrs, (fromDList -> binds :: [BindStmt], ast')) <- runOffer fillers . runWriter $ evac ast
+  MkOptions{preserveErrors} <- ask
+  for_ binds \bind -> tellPsError (psError (bindStmtExpr bind) preserveErrors) (bangSpan $ bindStmtSpan bind)
+  dflags <- ask
+  pure if null remainingErrs
+    then ast'
+    else panic $ unlines $ "Found extraneous bangs:" : (showPpr dflags <$> toList remainingErrs)
+  where
+    psError expr = \cases
+      Preserve      -> PsErrBangPatWithoutSpace expr
+      Don'tPreserve -> customError ErrBangOutsideOfDo
+
+evac :: forall a sig m . (Has Fill sig m, Data a) => Handler m a
+-- This recurses over all nodes in the AST, except for nodes for which
+-- one of the `try` functions returns `Just <something>`.
+evac e = maybe (gmapM evac e) pure =<< runMaybeT (tryEvac usualTries e)
+
+tryEvac :: Monad m => [Try m a] -> Try m a
+tryEvac tries = asum . (tries ??)
+
+usualTries :: (Has Fill sig m, Data a) => [Try m a]
+usualTries =
+  [ try @HsExpr, try @HsBindLR, try @MatchGroup, try @StmtLR
+  , ignore @RdrName, ignore @OccName, ignore @RealSrcSpan, ignore @EpAnnComments
+  ]
+
+-- As a minor performance optimization, we don't recurse over the AST if the node
+-- is a type that we know will never contain an expression
+ignore :: forall (e :: Type) m a . (Monad m, Typeable a, Typeable e) => Try m a
+ignore e = do
+  Refl <- hoistMaybe $ eqT @e @a
+  pure e
+
+-- | evacuate !s in pattern and collect all the names it binds
+evacPats :: forall a m sig . (Has (Fill :+: State InScope) sig m, Data a) => Handler m a
+evacPats e = do
+  currentState <- get @InScope
+  maybe (gmapM evacPats e) pure =<< runMaybeT (tryEvac ((local (<> currentState) .) <$> (try @Pat : usualTries)) e)
+
+-- | Find all !s in the given statements and combine the resulting bind
+-- statements into lists, with the original statements being the last one
+-- in each list - then concatenate these lists
+addStmts :: forall sig m . Has (PsErrors :+: HoleFills :+: Uniques :+: LocalVars :+: Reader DynFlags) sig m => Handler m [ExprLStmt GhcPs]
+addStmts = concatMapM \lstmt -> do
+  (fromDList -> stmts, lstmt') <- runWriter $ evac lstmt
+  pure $ map fromBindStmt stmts ++ [lstmt']
+
+type HoleFills = Offer Loc LExpr
+-- | We keep track of variables that are bound in lambdas, cases, etc., since
+-- these are variables that will not be accessible in the surrounding
+-- 'do'-block, and must therefore not be used.
+-- The Reader is used to find out what local variables are in scope, the Writer
+-- is used to inform callers which local variables have been bound.
+type LocalVars = Reader InScope :+: Writer Occs
+
+type Fill = PsErrors :+: Writer (DList BindStmt) :+: HoleFills :+: Uniques :+: LocalVars :+: Reader DynFlags
+
+data BindStmt = RdrName :<- LExpr
+
+bindStmtExpr :: BindStmt -> LExpr
+bindStmtExpr (_ :<- expr) = expr
+
+bindStmtSpan :: BindStmt -> SrcSpan
+bindStmtSpan = locA . \(_ :<- L l _) -> l
+
+fromBindStmt :: BindStmt -> ExprLStmt GhcPs
+fromBindStmt = noLocA . \cases
+  (var :<- lexpr) -> BindStmt noAnn varPat lexpr
+    where
+      varPat = noLocA . VarPat noExtField $ noLocA var
+
+-- | Use the !'d expression if it's short enough, or else abbreviate with `...`
+-- We don't need to worry about shadowing other !'d expressions:
+-- - For the user, we add line and column numbers to the name
+-- - For the compiler, we use a unique instead of the name
+bangVar :: Has (Uniques :+: Reader DynFlags) sig m => LExpr -> Loc -> m RdrName
+bangVar (L spn expr) loc = do
+  dflags <- ask
+  let name = '!' : case lines (showPpr dflags expr) of
+        (str:rest) | null rest && length str < 20 -> str
+                   | otherwise                    -> take 16 str ++ "..."
+        _                                         -> "<empty expression>"
+  locVar name (locA spn) loc
+
+locVar :: Has Uniques sig m => String -> SrcSpan -> Loc -> m RdrName
+locVar str spn loc = do
+  let occ = mkVarOcc $ printf "<%s:%d:%d>" str loc.line loc.col
+  unique <- freshUnique
+  pure . nameRdrName $ mkInternalName unique occ spn
+
+tellOne :: Has (Writer (DList w)) sig m => w -> m ()
+tellOne x = tell $ Endo (x:)
+
+tellLocalVar :: Has (Writer Occs) sig m => OccName -> m ()
+tellLocalVar = tell . unitOccs
src/MonadicBang/Internal/Effect/Offer.hs view
@@ -1,35 +1,35 @@-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE MonoLocalBinds #-}-{-# LANGUAGE OverloadedRecordDot #-}--module MonadicBang.Internal.Effect.Offer where--import Control.Algebra-import Control.Carrier.State.Strict-import Data.Map.Strict (Map)-import Data.Map.Strict qualified as M---- | Offers a number of things that can be yoinked, but only once-data Offer k v m a where-  Yoink :: k -> Offer k v m (Maybe v)--yoink :: Has (Offer k v) sig m => k -> m (Maybe v)-yoink = send . Yoink--newtype OfferC k v m a = OfferC {getOfferState :: StateC (Map k v) m a}-  deriving newtype (Functor, Applicative, Monad)---- Returns the result of the computation, along with the remaining offers-runOffer :: Map k v -> OfferC k v m a -> m (Map k v, a)-runOffer o (OfferC s) = runState o s--instance (Algebra sig m, Ord k) => Algebra (Offer k v :+: sig) (OfferC k v m) where-  alg hdl sig ctx = case sig of-    L (Yoink k) -> OfferC do-      (mv, remaining) <- M.updateLookupWithKey (\_ _ -> Nothing) k <$> get-      put remaining-      pure (mv <$ ctx)-    R other -> OfferC (alg ((.getOfferState) . hdl) (R other) ctx)-  {-# INLINE alg #-}+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE MonoLocalBinds #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+
+module MonadicBang.Internal.Effect.Offer where
+
+import Control.Algebra
+import Control.Carrier.State.Strict
+import Data.Map.Strict (Map)
+import Data.Map.Strict qualified as M
+
+-- | Offers a number of things that can be yoinked, but only once
+data Offer k v m a where
+  Yoink :: k -> Offer k v m (Maybe v)
+
+yoink :: Has (Offer k v) sig m => k -> m (Maybe v)
+yoink = send . Yoink
+
+newtype OfferC k v m a = OfferC {getOfferState :: StateC (Map k v) m a}
+  deriving newtype (Functor, Applicative, Monad)
+
+-- Returns the result of the computation, along with the remaining offers
+runOffer :: Map k v -> OfferC k v m a -> m (Map k v, a)
+runOffer o (OfferC s) = runState o s
+
+instance (Algebra sig m, Ord k) => Algebra (Offer k v :+: sig) (OfferC k v m) where
+  alg hdl sig ctx = case sig of
+    L (Yoink k) -> OfferC do
+      (mv, remaining) <- M.updateLookupWithKey (\_ _ -> Nothing) k <$> get
+      put remaining
+      pure (mv <$ ctx)
+    R other -> OfferC (alg ((.getOfferState) . hdl) (R other) ctx)
+  {-# INLINE alg #-}
src/MonadicBang/Internal/Effect/Uniques.hs view
@@ -1,41 +1,41 @@-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE MonoLocalBinds #-}-{-# LANGUAGE OverloadedRecordDot #-}--module MonadicBang.Internal.Effect.Uniques where--import Control.Algebra-import Control.Carrier.State.Strict-import Control.Monad.IO.Class-import Data.Functor-import Data.Tuple--import GHC.Types.Unique-import GHC.Types.Unique.Supply---- | Uniques provides arbitrarily many unique GHC Uniques-data Uniques m a where-  FreshUnique :: Uniques m Unique--freshUnique :: Has Uniques sig m => m Unique-freshUnique = send FreshUnique--newtype UniquesC m a = UniquesC {getUniquesState :: StateC UniqSupply m a}-  deriving newtype (Functor, Applicative, Monad)---- | The "mask" (Char) supplied is purely cosmetic, making it easier to figure out where a Unique was born.------ See Note [Uniques for wired-in prelude things and known masks] in GHC.Builtin.Uniques-runUniquesIO :: MonadIO m => Char -> UniquesC m a -> m a-runUniquesIO mask (UniquesC s) = flip evalState s =<< liftIO (mkSplitUniqSupply mask)--runUniques :: Functor m => UniqSupply -> UniquesC m a -> m a-runUniques uniqSupply (UniquesC s) = evalState uniqSupply s--instance Algebra sig m => Algebra (Uniques :+: sig) (UniquesC m) where-  alg hdl sig ctx = case sig of-    L FreshUnique -> UniquesC . state $ fmap (ctx $>) . swap . takeUniqFromSupply-    R other -> UniquesC (alg ((.getUniquesState) . hdl) (R other) ctx)-  {-# INLINE alg #-} +{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE MonoLocalBinds #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+
+module MonadicBang.Internal.Effect.Uniques where
+
+import Control.Algebra
+import Control.Carrier.State.Strict
+import Control.Monad.IO.Class
+import Data.Functor
+import Data.Tuple
+
+import GHC.Types.Unique
+import GHC.Types.Unique.Supply
+
+-- | Uniques provides arbitrarily many unique GHC Uniques
+data Uniques m a where
+  FreshUnique :: Uniques m Unique
+
+freshUnique :: Has Uniques sig m => m Unique
+freshUnique = send FreshUnique
+
+newtype UniquesC m a = UniquesC {getUniquesState :: StateC UniqSupply m a}
+  deriving newtype (Functor, Applicative, Monad)
+
+-- | The "mask" (Char) supplied is purely cosmetic, making it easier to figure out where a Unique was born.
+--
+-- See Note [Uniques for wired-in prelude things and known masks] in GHC.Builtin.Uniques
+runUniquesIO :: MonadIO m => Char -> UniquesC m a -> m a
+runUniquesIO mask (UniquesC s) = flip evalState s =<< liftIO (mkSplitUniqSupply mask)
+
+runUniques :: Functor m => UniqSupply -> UniquesC m a -> m a
+runUniques uniqSupply (UniquesC s) = evalState uniqSupply s
+
+instance Algebra sig m => Algebra (Uniques :+: sig) (UniquesC m) where
+  alg hdl sig ctx = case sig of
+    L FreshUnique -> UniquesC . state $ fmap (ctx $>) . swap . takeUniqFromSupply
+    R other -> UniquesC (alg ((.getUniquesState) . hdl) (R other) ctx)
+  {-# INLINE alg #-} 
src/MonadicBang/Internal/Effect/Writer/Discard.hs view
@@ -1,24 +1,24 @@-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE GADTs #-}---- | A Writer carrier that discards any values it is told-module MonadicBang.Internal.Effect.Writer.Discard where--import Control.Algebra-import Control.Effect.Writer--newtype DiscardC w m a = DiscardC { evalDiscardC :: m a }-  deriving newtype (Functor, Applicative, Monad)--evalWriter :: (Monoid w, Algebra sig m) => DiscardC w m a -> m a-evalWriter = evalDiscardC--instance (Monoid w, Algebra sig m) => Algebra (Writer w :+: sig) (DiscardC w m) where-  alg hdl sig ctx = DiscardC $ case sig of-    L writer -> case writer of-      Tell _ -> pure ctx-      Listen m -> fmap (mempty,) <$> evalWriter (hdl (m <$ ctx))-      Censor _ m -> evalWriter (hdl (m <$ ctx))-    R other -> alg (evalDiscardC . hdl) other ctx+{-# LANGUAGE DerivingStrategies #-}
+{-# LANGUAGE UndecidableInstances #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE GADTs #-}
+
+-- | A Writer carrier that discards any values it is told
+module MonadicBang.Internal.Effect.Writer.Discard where
+
+import Control.Algebra
+import Control.Effect.Writer
+
+newtype DiscardC w m a = DiscardC { evalDiscardC :: m a }
+  deriving newtype (Functor, Applicative, Monad)
+
+evalWriter :: (Monoid w, Algebra sig m) => DiscardC w m a -> m a
+evalWriter = evalDiscardC
+
+instance (Monoid w, Algebra sig m) => Algebra (Writer w :+: sig) (DiscardC w m) where
+  alg hdl sig ctx = DiscardC $ case sig of
+    L writer -> case writer of
+      Tell _ -> pure ctx
+      Listen m -> fmap (mempty,) <$> evalWriter (hdl (m <$ ctx))
+      Censor _ m -> evalWriter (hdl (m <$ ctx))
+    R other -> alg (evalDiscardC . hdl) other ctx
src/MonadicBang/Internal/Error.hs view
@@ -1,49 +1,49 @@-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE CPP #-}--module MonadicBang.Internal.Error where--import Prelude hiding ((<>))--import Control.Effect.Writer--import GHC-#if MIN_VERSION_ghc(9,8,0)-import GHC.Utils.Error-#endif-import GHC.Types.Error-import GHC.Types.Name.Occurrence-import GHC.Parser.Errors.Types-import GHC.Utils.Outputable--data Error = ErrOutOfScopeVariable OccName-           | ErrBangOutsideOfDo--type PsErrors = Writer (Messages PsError)--customError :: Error -> PsError-#if MIN_VERSION_ghc(9,8,0)-customError = PsUnknownMessage . mkUnknownDiagnostic . \cases-#elif MIN_VERSION_ghc(9,6,0)-customError = PsUnknownMessage . UnknownDiagnostic . \cases-#else-customError = PsUnknownMessage . \cases-#endif-  ErrBangOutsideOfDo -> DiagnosticMessage-    { diagMessage = mkDecorated [text "Monadic ! outside of a 'do'-block is not allowed"]-    , diagReason = ErrorWithoutFlag-    , diagHints = [SuggestMissingDo]-    }-  (ErrOutOfScopeVariable name) -> DiagnosticMessage-    { diagMessage = mkDecorated [text "The variable " <> quotes (ppr name) <> text " cannot be used inside of ! here, since its desugaring would escape its scope"]-    , diagReason = ErrorWithoutFlag-    , diagHints = [UnknownHint $ text "Maybe you meant to open a new 'do'-block after " <> ppr name <> text " has been bound?"]-    }--tellPsError :: Has PsErrors sig m => PsError -> SrcSpan -> m ()-tellPsError err srcSpan = tell . singleMessage $-#if MIN_VERSION_ghc(9,8,0)-  mkErrorMsgEnvelope srcSpan neverQualify err-#else-  MsgEnvelope srcSpan neverQualify err SevError-#endif+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE CPP #-}
+
+module MonadicBang.Internal.Error where
+
+import Prelude hiding ((<>))
+
+import Control.Effect.Writer
+
+import GHC
+#if MIN_VERSION_ghc(9,8,0)
+import GHC.Utils.Error
+#endif
+import GHC.Types.Error
+import GHC.Types.Name.Occurrence
+import GHC.Parser.Errors.Types
+import GHC.Utils.Outputable
+
+data Error = ErrOutOfScopeVariable OccName
+           | ErrBangOutsideOfDo
+
+type PsErrors = Writer (Messages PsError)
+
+customError :: Error -> PsError
+#if MIN_VERSION_ghc(9,8,0)
+customError = PsUnknownMessage . mkUnknownDiagnostic . \cases
+#elif MIN_VERSION_ghc(9,6,0)
+customError = PsUnknownMessage . UnknownDiagnostic . \cases
+#else
+customError = PsUnknownMessage . \cases
+#endif
+  ErrBangOutsideOfDo -> DiagnosticMessage
+    { diagMessage = mkDecorated [text "Monadic ! outside of a 'do'-block is not allowed"]
+    , diagReason = ErrorWithoutFlag
+    , diagHints = [SuggestMissingDo]
+    }
+  (ErrOutOfScopeVariable name) -> DiagnosticMessage
+    { diagMessage = mkDecorated [text "The variable " <> quotes (ppr name) <> text " cannot be used inside of ! here, since its desugaring would escape its scope"]
+    , diagReason = ErrorWithoutFlag
+    , diagHints = [UnknownHint $ text "Maybe you meant to open a new 'do'-block after " <> ppr name <> text " has been bound?"]
+    }
+
+tellPsError :: Has PsErrors sig m => PsError -> SrcSpan -> m ()
+tellPsError err srcSpan = tell . singleMessage $
+#if MIN_VERSION_ghc(9,8,0)
+  mkErrorMsgEnvelope srcSpan neverQualify err
+#else
+  MsgEnvelope srcSpan neverQualify err SevError
+#endif
src/MonadicBang/Internal/Options.hs view
@@ -1,61 +1,61 @@-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE LambdaCase #-}-{-# LANGUAGE OverloadedRecordDot #-}-{-# LANGUAGE CPP #-}--module MonadicBang.Internal.Options where--import Control.Exception-import Control.Algebra-import Control.Carrier.State.Strict-import Control.Effect.Throw-import Control.Monad-import Data.Bifunctor-import Data.Bool-import Data.List (intercalate, partition)--import GHC-import GHC.Plugins--data Verbosity = DumpTransformed | Quiet--data PreserveErrors = Preserve | Don'tPreserve--data Options = MkOptions {verbosity :: Verbosity, preserveErrors :: PreserveErrors}--#if MIN_VERSION_ghc(9,6,0)-parseOptions :: Has (Throw ErrorCall) sig m => Located (HsModule GhcPs) -> [CommandLineOption] -> m Options-#else-parseOptions :: Has (Throw ErrorCall) sig m => Located HsModule -> [CommandLineOption] -> m Options-#endif-parseOptions mod' cmdLineOpts = do-  (remaining, options) <- runState cmdLineOpts do-    verbosity <- bool Quiet DumpTransformed <$> extractOpts verboseOpts-    preserveErrors <- bool Don'tPreserve Preserve <$> extractOpts preserveErrorsOpts-    pure $ MkOptions verbosity preserveErrors-  unless (null remaining) . throwError . ErrorCall $-    "Incorrect command line options for plugin MonadicBang, encountered in " ++ modName ++ modFile ++-    "\n\tOptions that were supplied (via -fplugin-opt) are: " ++ intercalate ", " (map show cmdLineOpts) ++-    "\n\tUnrecognized options: " ++ showOpts remaining ++-    "\n\n\tUsage: [-ddump] [-preserve-errors]" ++-    "\n" ++-    "\n\t\t-ddump            Print the altered AST" ++-    "\n\t\t-preserve-errors  Keep parse errors about ! outside of 'do' in their original form, rather then a more relevant explanation." ++-    "\n\t\t                  This is mainly useful if another plugin expects those errors."-  pure options--  where-    verboseOpts = ["-ddump"]-    preserveErrorsOpts = ["-preserve-errors"]-    extractOpts opt = do-      (isOpt, opts') <- gets $ first (not . null) . partition (`elem` opt)-      put opts'-      pure isOpt--    showOpts = intercalate ", " . map show--    modFile = maybe "" ((" in file " ++) . unpackFS . srcSpanFile) $ toRealSrcSpan (getLoc mod')-    modName = maybe "an unnamed module" (("module " ++) . moduleNameString . unLoc) $ (unLoc mod').hsmodName-    toRealSrcSpan = \cases-      (RealSrcSpan rss _) -> Just rss-      (UnhelpfulSpan _) -> Nothing+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE LambdaCase #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE CPP #-}
+
+module MonadicBang.Internal.Options where
+
+import Control.Exception
+import Control.Algebra
+import Control.Carrier.State.Strict
+import Control.Effect.Throw
+import Control.Monad
+import Data.Bifunctor
+import Data.Bool
+import Data.List (intercalate, partition)
+
+import GHC
+import GHC.Plugins
+
+data Verbosity = DumpTransformed | Quiet
+
+data PreserveErrors = Preserve | Don'tPreserve
+
+data Options = MkOptions {verbosity :: Verbosity, preserveErrors :: PreserveErrors}
+
+#if MIN_VERSION_ghc(9,6,0)
+parseOptions :: Has (Throw ErrorCall) sig m => Located (HsModule GhcPs) -> [CommandLineOption] -> m Options
+#else
+parseOptions :: Has (Throw ErrorCall) sig m => Located HsModule -> [CommandLineOption] -> m Options
+#endif
+parseOptions mod' cmdLineOpts = do
+  (remaining, options) <- runState cmdLineOpts do
+    verbosity <- bool Quiet DumpTransformed <$> extractOpts verboseOpts
+    preserveErrors <- bool Don'tPreserve Preserve <$> extractOpts preserveErrorsOpts
+    pure $ MkOptions verbosity preserveErrors
+  unless (null remaining) . throwError . ErrorCall $
+    "Incorrect command line options for plugin MonadicBang, encountered in " ++ modName ++ modFile ++
+    "\n\tOptions that were supplied (via -fplugin-opt) are: " ++ intercalate ", " (map show cmdLineOpts) ++
+    "\n\tUnrecognized options: " ++ showOpts remaining ++
+    "\n\n\tUsage: [-ddump] [-preserve-errors]" ++
+    "\n" ++
+    "\n\t\t-ddump            Print the altered AST" ++
+    "\n\t\t-preserve-errors  Keep parse errors about ! outside of 'do' in their original form, rather then a more relevant explanation." ++
+    "\n\t\t                  This is mainly useful if another plugin expects those errors."
+  pure options
+
+  where
+    verboseOpts = ["-ddump"]
+    preserveErrorsOpts = ["-preserve-errors"]
+    extractOpts opt = do
+      (isOpt, opts') <- gets $ first (not . null) . partition (`elem` opt)
+      put opts'
+      pure isOpt
+
+    showOpts = intercalate ", " . map show
+
+    modFile = maybe "" ((" in file " ++) . unpackFS . srcSpanFile) $ toRealSrcSpan (getLoc mod')
+    modName = maybe "an unnamed module" (("module " ++) . moduleNameString . unLoc) $ (unLoc mod').hsmodName
+    toRealSrcSpan = \cases
+      (RealSrcSpan rss _) -> Just rss
+      (UnhelpfulSpan _) -> Nothing
src/MonadicBang/Internal/Utils.hs view
@@ -1,38 +1,38 @@-{-# LANGUAGE MonoLocalBinds #-}-{-# LANGUAGE CPP #-}--module MonadicBang.Internal.Utils where--import Control.Monad.Trans.Maybe-import Data.Monoid-import GHC.Stack (withFrozenCallStack, HasCallStack)--type DList a = Endo [a]---- | Handle a specific AST node-type Handler m a = a -> m a---- | Try handling an AST node, but may fail (usually because the handler is not--- applicable)-type Try m a = Handler (MaybeT m) a--{-# INLINE fromDList #-}-fromDList :: DList a -> [a]-fromDList = appEndo ?? []--{-# INLINE (??) #-}-(??) :: Functor f => f (a -> b) -> a -> f b-fs ?? x = ($ x) <$> fs--#if MIN_VERSION_transformers(0,6,0)-#else-{-# INLINE hoistMaybe #-}-hoistMaybe :: Applicative m => Maybe a -> MaybeT m a-hoistMaybe = MaybeT . pure-#endif--panic :: HasCallStack => String -> a-panic message = withFrozenCallStack $ error $-  unlines ["MonadicBang panic:", message, "", submitReport]-  where-    submitReport = "This is likely a bug. Please submit a bug report at https://github.com/JakobBruenker/monadic-bang/issues"+{-# LANGUAGE MonoLocalBinds #-}
+{-# LANGUAGE CPP #-}
+
+module MonadicBang.Internal.Utils where
+
+import Control.Monad.Trans.Maybe
+import Data.Monoid
+import GHC.Stack (withFrozenCallStack, HasCallStack)
+
+type DList a = Endo [a]
+
+-- | Handle a specific AST node
+type Handler m a = a -> m a
+
+-- | Try handling an AST node, but may fail (usually because the handler is not
+-- applicable)
+type Try m a = Handler (MaybeT m) a
+
+{-# INLINE fromDList #-}
+fromDList :: DList a -> [a]
+fromDList = appEndo ?? []
+
+{-# INLINE (??) #-}
+(??) :: Functor f => f (a -> b) -> a -> f b
+fs ?? x = ($ x) <$> fs
+
+#if MIN_VERSION_transformers(0,6,0)
+#else
+{-# INLINE hoistMaybe #-}
+hoistMaybe :: Applicative m => Maybe a -> MaybeT m a
+hoistMaybe = MaybeT . pure
+#endif
+
+panic :: HasCallStack => String -> a
+panic message = withFrozenCallStack $ error $
+  unlines ["MonadicBang panic:", message, "", submitReport]
+  where
+    submitReport = "This is likely a bug. Please submit a bug report at https://github.com/JakobBruenker/monadic-bang/issues"
test/MonadicBang/Test.hs view
@@ -1,51 +1,51 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE LexicalNegation #-}-{-# LANGUAGE MonadComprehensions #-}-{-# LANGUAGE ViewPatterns #-}--module Main (main) where--import Prelude hiding ((<>))-#if MIN_VERSION_ghc(9,6,0)-#else-import Control.Applicative (liftA2)-#endif-import Data.Foldable-import Data.Traversable-import System.IO--import MonadicBang.Test.Utils-import MonadicBang.Test.ShouldPass-import MonadicBang.Test.ShouldFail--import GHC.Utils.Outputable-import GHC.Utils.Ppr (Mode(PageMode))-import System.Exit (exitFailure)--main :: IO ()-main = do-  (numFailures, numFailedSuites) <- liftA2 (,) sum (length . filter (> 0)) <$> for suites \suite -> do-    failures <- runSuite suite-    for_ failures \failure -> putSDoc stderr $ vcat [space, prettyFail failure]-    pure $ length failures-  if numFailures == 0-    then putStrLn "All tests passed!"-    else do-      putSDoc stdout $-        space $+$ plural' numFailures "test" <+> text "in" <+> int numFailedSuites <> char '/' <> plural' (length suites) "suite" <+> text "failed."-      exitFailure-  where-    plural' :: Int -> String -> SDoc-    plural' n (text -> s) = int n <+> case n of-      1 -> s-      _ -> s <> char 's'--putSDoc :: Handle -> SDoc -> IO ()-putSDoc = printSDocLn defaultSDocContext (PageMode True)--suites :: [TestType]-suites =-  [ shouldPass-  , shouldFail-  ]+{-# LANGUAGE CPP #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE LexicalNegation #-}
+{-# LANGUAGE MonadComprehensions #-}
+{-# LANGUAGE ViewPatterns #-}
+
+module Main (main) where
+
+import Prelude hiding ((<>))
+#if MIN_VERSION_ghc(9,6,0)
+#else
+import Control.Applicative (liftA2)
+#endif
+import Data.Foldable
+import Data.Traversable
+import System.IO
+
+import MonadicBang.Test.Utils
+import MonadicBang.Test.ShouldPass
+import MonadicBang.Test.ShouldFail
+
+import GHC.Utils.Outputable
+import GHC.Utils.Ppr (Mode(PageMode))
+import System.Exit (exitFailure)
+
+main :: IO ()
+main = do
+  (numFailures, numFailedSuites) <- liftA2 (,) sum (length . filter (> 0)) <$> for suites \suite -> do
+    failures <- runSuite suite
+    for_ failures \failure -> putSDoc stderr $ vcat [space, prettyFail failure]
+    pure $ length failures
+  if numFailures == 0
+    then putStrLn "All tests passed!"
+    else do
+      putSDoc stdout $
+        space $+$ plural' numFailures "test" <+> text "in" <+> int numFailedSuites <> char '/' <> plural' (length suites) "suite" <+> text "failed."
+      exitFailure
+  where
+    plural' :: Int -> String -> SDoc
+    plural' n (text -> s) = int n <+> case n of
+      1 -> s
+      _ -> s <> char 's'
+
+putSDoc :: Handle -> SDoc -> IO ()
+putSDoc = printSDocLn defaultSDocContext (PageMode True)
+
+suites :: [TestType]
+suites =
+  [ shouldPass
+  , shouldFail
+  ]
test/MonadicBang/Test/ShouldFail.hs view
@@ -1,64 +1,64 @@-{-# LANGUAGE LambdaCase #-}--module MonadicBang.Test.ShouldFail (shouldFail) where--import MonadicBang.Test.Utils-import MonadicBang.Internal.Error--import GHC.Types.Name.Occurrence--import GHC.Parser.Errors.Types--shouldFail :: Test-shouldFail = do-  combined-  various-  letStmt-  letInLet--data ErrorData-  = S String -- ^ Out of scope variable-  | O        -- ^ Bang outside of do--mkErrors :: [ErrorData] -> [PsMessage]-mkErrors = map (customError . toError)-  where-    toError = \cases-      (S var) -> ErrOutOfScopeVariable $ mkVarOcc var-      O -> ErrBangOutsideOfDo--combined :: Test-combined = assertParseFailWith (mkErrors [S "x", S "f", S "a", S "b", S "b", O, O]) "\-\!(!do\n\-\  x <- getA\n\-\  let y = let x = print 24 in !x\n\-\  let f (a, b) = !(f a) + !(let c = c + b in c + b + z)\n\-\  pure y)\n\-\"--various :: Test-various = assertParseFailWith (mkErrors [S "a", S "x", S "y", S "b", S "b1", S "a2", O, O, O]) "\-\main = !getA\n\-\g = do let a = x in !a\n\-\       pure ()\n\-\f = !getB\n\-\h = \\x -> !x\n\-\i = do \\y -> !y\n\-\j = let z = z in do \\_ -> !z -- no error\n\-\k = case () of a -> do case () of b -> !(a + b)\n\-\l = let c1 = c1 in do let b1 = b1 in !(let c1 = c1 in a1 + b1 + c1)\n\-\m = do !(let a2 = a2 in !a2)\n\-\"--letStmt :: Test-letStmt = assertParseFailWith (mkErrors [S "x"]) "\-\main = do\n\-\  let x = !x\n\-\  pure x\n\-\"--letInLet :: Test-letInLet = assertParseFailWith (mkErrors [S "y", S "x"]) "\-\main = do\n\-\  let x _ = x in let y = y in !y + !x\n\-\"+{-# LANGUAGE LambdaCase #-}
+
+module MonadicBang.Test.ShouldFail (shouldFail) where
+
+import MonadicBang.Test.Utils
+import MonadicBang.Internal.Error
+
+import GHC.Types.Name.Occurrence
+
+import GHC.Parser.Errors.Types
+
+shouldFail :: Test
+shouldFail = do
+  combined
+  various
+  letStmt
+  letInLet
+
+data ErrorData
+  = S String -- ^ Out of scope variable
+  | O        -- ^ Bang outside of do
+
+mkErrors :: [ErrorData] -> [PsMessage]
+mkErrors = map (customError . toError)
+  where
+    toError = \cases
+      (S var) -> ErrOutOfScopeVariable $ mkVarOcc var
+      O -> ErrBangOutsideOfDo
+
+combined :: Test
+combined = assertParseFailWith (mkErrors [S "x", S "f", S "a", S "b", S "b", O, O]) "\
+\!(!do\n\
+\  x <- getA\n\
+\  let y = let x = print 24 in !x\n\
+\  let f (a, b) = !(f a) + !(let c = c + b in c + b + z)\n\
+\  pure y)\n\
+\"
+
+various :: Test
+various = assertParseFailWith (mkErrors [S "a", S "x", S "y", S "b", S "b1", S "a2", O, O, O]) "\
+\main = !getA\n\
+\g = do let a = x in !a\n\
+\       pure ()\n\
+\f = !getB\n\
+\h = \\x -> !x\n\
+\i = do \\y -> !y\n\
+\j = let z = z in do \\_ -> !z -- no error\n\
+\k = case () of a -> do case () of b -> !(a + b)\n\
+\l = let c1 = c1 in do let b1 = b1 in !(let c1 = c1 in a1 + b1 + c1)\n\
+\m = do !(let a2 = a2 in !a2)\n\
+\"
+
+letStmt :: Test
+letStmt = assertParseFailWith (mkErrors [S "x"]) "\
+\main = do\n\
+\  let x = !x\n\
+\  pure x\n\
+\"
+
+letInLet :: Test
+letInLet = assertParseFailWith (mkErrors [S "y", S "x"]) "\
+\main = do\n\
+\  let x _ = x in let y = y in !y + !x\n\
+\"
test/MonadicBang/Test/ShouldPass.hs view
@@ -1,153 +1,153 @@-{-# LANGUAGE RecursiveDo #-}-{-# LANGUAGE QualifiedDo #-}-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE MultiWayIf #-}-{-# LANGUAGE ViewPatterns #-}-{-# LANGUAGE ParallelListComp #-}-{-# LANGUAGE MonadComprehensions #-}-{-# LANGUAGE NegativeLiterals #-}--{-# OPTIONS_GHC -fplugin=MonadicBang #-}--module MonadicBang.Test.ShouldPass where- -import Data.Char-import Control.Monad.Trans.State--import MonadicBang.Test.Utils-import MonadicBang.Test.Utils.QualifiedDo qualified as QualifiedDo-import Control.Monad.IO.Class--shouldPass :: Test-shouldPass = do-  simpleDo-  insideDo-  insideMDo-  insideRec-  nested-  lambda-  insideLet-  listComp-  monadComp-  parListComp-  multiWayIf-  guards-  viewPat-  insideWhere-  insideCase-  usingDoBlockVar-  largeExpr-  confusing-  qualifiedDo--getA, getB, getC :: MonadIO m => m String-getA = pure "a"-getB = pure "b"-getC = pure "c"--simpleDo :: Test-simpleDo = do assertEq "a" !getA--insideDo :: Test-insideDo = do-  let ioA = getA-      nonIOC = !getC-  assertEq "abc" (!ioA ++ !ioB ++ nonIOC)-  where-    ioB = getB--insideMDo :: Test-insideMDo = assertEq (Just $ replicate @Int 10 -1) $ take 10 <$> mdo-  xs <- Just (1:xs)-  pure (negate <$> !(pure xs))--insideRec :: Test-insideRec = assertEq (Just $ take @Int 10 $ cycle [1, -1]) $ take 10 <$> do-  rec xs <- Just (1:ys)-      ys <- pure (negate <$> !(pure xs))-  pure xs--nested :: Test-nested = do assertEq "Ab"-                     !(pure (!(fmap toUpper <$> !(pure getA)) ++ !(!(pure getB))))--lambda :: Test-lambda = do assertEq "abc!" $ ((\a -> a ++ !getB) !getA) ++ !((\c -> do pure (!c ++ "!")) getC)--insideLet :: Test-insideLet = do-  assertEq "abc" !do-    let a = !getA-    let b _ = !getB-    let c = !getC in pure (a ++ b b ++ c)--listComp :: Test-listComp = assertEq @[Int]-  [101, 102, 103, 201, 202, 203, 301, 302, 303]-  [ ![1,2,3] + y | let y = ![100,200,300] ]--monadComp :: Test-monadComp = do assertEq "abc" ![ !getA ++ b ++ c | let b = !getB, c <- getC ]--parListComp :: Test-parListComp = assertEq @[Int]-  [11111, 21111, 12111, 22111, 11221, 21221, 12221, 22221]-  [ x + y + w + ![1000,2000] + ![10000,20000] | let x = ![1,2], let w = ![10,20] | let y = ![100,200] ]--guards :: Test-guards | [2,3,4] <- do [![1,2,3] + 1 :: Int] = pure ()-       | otherwise = error "guards didn't match"--viewPat :: Test-viewPat = assertEq 9999 x-  where (do pure (!succ * !pred) -> x) = 100 :: Int--insideWhere :: Test-insideWhere = do-  c <- getC-  assertEq "[2,3,4]c" $ show list ++ c-  where-    list = do [![1,2,3] + 1 :: Int]--insideCase :: Test-insideCase = do-  assertEq "b"-    case !getA of-      (!(pure (++ "_")) -> "d") -> c "abc" ++ s123-        where c a = !getC ++ a-              s123 = do pure !"123"-      "c" -> "d"-      _a -> "b"--multiWayIf :: Test-multiWayIf = do-  assertEq "b" if-    | !getA == !getA -> !getB-    | otherwise      -> !getC--usingDoBlockVar :: Test-usingDoBlockVar = do-  let a = !getA-  assertEq "a" !(pure a)--largeExpr :: Test-largeExpr = do-  assertEq () !(assertEq () !(assertEq "abc" ![ !getA ++ b ++ c | let b = !getB, c <- getC ]))--confusing :: Test-confusing = do-  assertEq @Int 4 $ flip evalState 0 do-    put 4-    put 5 >> pure !get-  assertEq @Int 5 $ flip evalState 0 do-    put 4-    put 5-    pure !get --qualifiedDo :: Test-qualifiedDo = do-  assertEq (5 + 10 + 20 + (5 + 20)) QualifiedDo.do-    x <- 5-    10-    y <- 20-    x + y+{-# LANGUAGE RecursiveDo #-}
+{-# LANGUAGE QualifiedDo #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE MultiWayIf #-}
+{-# LANGUAGE ViewPatterns #-}
+{-# LANGUAGE ParallelListComp #-}
+{-# LANGUAGE MonadComprehensions #-}
+{-# LANGUAGE NegativeLiterals #-}
+
+{-# OPTIONS_GHC -fplugin=MonadicBang #-}
+
+module MonadicBang.Test.ShouldPass where
+ 
+import Data.Char
+import Control.Monad.Trans.State
+
+import MonadicBang.Test.Utils
+import MonadicBang.Test.Utils.QualifiedDo qualified as QualifiedDo
+import Control.Monad.IO.Class
+
+shouldPass :: Test
+shouldPass = do
+  simpleDo
+  insideDo
+  insideMDo
+  insideRec
+  nested
+  lambda
+  insideLet
+  listComp
+  monadComp
+  parListComp
+  multiWayIf
+  guards
+  viewPat
+  insideWhere
+  insideCase
+  usingDoBlockVar
+  largeExpr
+  confusing
+  qualifiedDo
+
+getA, getB, getC :: MonadIO m => m String
+getA = pure "a"
+getB = pure "b"
+getC = pure "c"
+
+simpleDo :: Test
+simpleDo = do assertEq "a" !getA
+
+insideDo :: Test
+insideDo = do
+  let ioA = getA
+      nonIOC = !getC
+  assertEq "abc" (!ioA ++ !ioB ++ nonIOC)
+  where
+    ioB = getB
+
+insideMDo :: Test
+insideMDo = assertEq (Just $ replicate @Int 10 -1) $ take 10 <$> mdo
+  xs <- Just (1:xs)
+  pure (negate <$> !(pure xs))
+
+insideRec :: Test
+insideRec = assertEq (Just $ take @Int 10 $ cycle [1, -1]) $ take 10 <$> do
+  rec xs <- Just (1:ys)
+      ys <- pure (negate <$> !(pure xs))
+  pure xs
+
+nested :: Test
+nested = do assertEq "Ab"
+                     !(pure (!(fmap toUpper <$> !(pure getA)) ++ !(!(pure getB))))
+
+lambda :: Test
+lambda = do assertEq "abc!" $ ((\a -> a ++ !getB) !getA) ++ !((\c -> do pure (!c ++ "!")) getC)
+
+insideLet :: Test
+insideLet = do
+  assertEq "abc" !do
+    let a = !getA
+    let b _ = !getB
+    let c = !getC in pure (a ++ b b ++ c)
+
+listComp :: Test
+listComp = assertEq @[Int]
+  [101, 102, 103, 201, 202, 203, 301, 302, 303]
+  [ ![1,2,3] + y | let y = ![100,200,300] ]
+
+monadComp :: Test
+monadComp = do assertEq "abc" ![ !getA ++ b ++ c | let b = !getB, c <- getC ]
+
+parListComp :: Test
+parListComp = assertEq @[Int]
+  [11111, 21111, 12111, 22111, 11221, 21221, 12221, 22221]
+  [ x + y + w + ![1000,2000] + ![10000,20000] | let x = ![1,2], let w = ![10,20] | let y = ![100,200] ]
+
+guards :: Test
+guards | [2,3,4] <- do [![1,2,3] + 1 :: Int] = pure ()
+       | otherwise = error "guards didn't match"
+
+viewPat :: Test
+viewPat = assertEq 9999 x
+  where (do pure (!succ * !pred) -> x) = 100 :: Int
+
+insideWhere :: Test
+insideWhere = do
+  c <- getC
+  assertEq "[2,3,4]c" $ show list ++ c
+  where
+    list = do [![1,2,3] + 1 :: Int]
+
+insideCase :: Test
+insideCase = do
+  assertEq "b"
+    case !getA of
+      (!(pure (++ "_")) -> "d") -> c "abc" ++ s123
+        where c a = !getC ++ a
+              s123 = do pure !"123"
+      "c" -> "d"
+      _a -> "b"
+
+multiWayIf :: Test
+multiWayIf = do
+  assertEq "b" if
+    | !getA == !getA -> !getB
+    | otherwise      -> !getC
+
+usingDoBlockVar :: Test
+usingDoBlockVar = do
+  let a = !getA
+  assertEq "a" !(pure a)
+
+largeExpr :: Test
+largeExpr = do
+  assertEq () !(assertEq () !(assertEq "abc" ![ !getA ++ b ++ c | let b = !getB, c <- getC ]))
+
+confusing :: Test
+confusing = do
+  assertEq @Int 4 $ flip evalState 0 do
+    put 4
+    put 5 >> pure !get
+  assertEq @Int 5 $ flip evalState 0 do
+    put 4
+    put 5
+    pure !get 
+
+qualifiedDo :: Test
+qualifiedDo = do
+  assertEq (5 + 10 + 20 + (5 + 20)) QualifiedDo.do
+    x <- 5
+    10
+    y <- 20
+    x + y
test/MonadicBang/Test/Utils.hs view
@@ -1,100 +1,100 @@-{-# LANGUAGE CPP #-}-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE LambdaCase #-}-#if MIN_VERSION_ghc(9,6,0)-{-# LANGUAGE ScopedTypeVariables #-}-#endif-{-# LANGUAGE NoFieldSelectors #-}-{-# LANGUAGE OverloadedRecordDot #-}-{-# LANGUAGE ViewPatterns #-}--module MonadicBang.Test.Utils where--import Control.Monad-import Data.Foldable-import Data.Function--import Control.Monad.Trans.Writer.CPS--import GHC.Stack--import GHC-import GHC.Driver.Errors.Types-import GHC.Types.Error-import GHC.Types.SourceError-import GHC.Utils.Outputable hiding ((<>))--import MonadicBang.Test.Utils.RunGhcParser-import MonadicBang.Internal.Utils-import Data.Monoid--data FailType-  = forall a . Show a => IncorrectResult { expectedValue :: a, actualValue :: a }-  | forall a . Outputable a => Didn'tFail { expectedFails :: [PsMessage], actualValue :: a }-  | FailedIncorrectly { expectedFails :: [PsMessage], actualFails :: [GhcMessage] }--data Fail = MkFail { error :: FailType, callStack :: CallStack }--type TestType = WriterT (DList Fail) IO ()--type Test = HasCallStack => TestType--runSuite :: TestType -> IO [Fail]-runSuite test = fromDList <$> execWriterT test--prettyFail :: Fail -> SDoc-prettyFail failure = vcat-  [ case failure.error of-      IncorrectResult{ expectedValue, actualValue } -> vcat-        [ text "Expected: " <+> text (show expectedValue)-        , text "but got:  " <+> text (show actualValue)-        ]-      Didn'tFail{ expectedFails } -> vcat-        [ text "Expected failure with"-        , nest 2 $ diagnosticsSDoc expectedFails-        , text "but execution succeeded"-        ]-      FailedIncorrectly{ expectedFails, actualFails } -> vcat-        [ text "Expected failure with"-        , nest 2 $ diagnosticsSDoc expectedFails-        , text "but execution failed with these errors instead:"-        , nest 2 $ diagnosticsSDoc actualFails-        ]-  , text "at" <+> text (prettyCallStack failure.callStack)-  ]-  where-    diagnosticsSDoc diags = vcat (map (vcat . unDecorated . diagMsg) diags)--recordFail :: HasCallStack => FailType -> TestType-recordFail err = tell . Endo . (:) $ MkFail err callStack--assertEq :: (HasCallStack, Show a, Eq a) => a -> a -> TestType-assertEq expected actual = when (expected /= actual) $-  withFrozenCallStack $ recordFail $ IncorrectResult expected actual--sdocEq :: SDoc -> SDoc -> Bool-sdocEq = (==) `on` showSDocUnsafe--assertFailWith :: (HasCallStack, Outputable a) => [PsMessage] -> Either SourceError a -> TestType-assertFailWith expected = \case-  Right result -> withFrozenCallStack $ recordFail $ Didn'tFail expected result-  Left err -> unless sameFails do-    withFrozenCallStack $ recordFail $ FailedIncorrectly expected errMsgs-    where-      errMsgs = toList (srcErrorMessages err)-      toPsMessage = \case-        GhcPsMessage m -> Just m-        _ -> Nothing-      listEq eq xs ys = and $ zipWith eq xs ys-      sameFails = maybe False (((listEq . listEq) sdocEq `on` map (unDecorated . diagMsg)) expected) $ traverse toPsMessage errMsgs--diagMsg :: forall a . Diagnostic a => a -> DecoratedSDoc-#if MIN_VERSION_ghc(9,6,0)-diagMsg = diagnosticMessage (defaultDiagnosticOpts @a)-#else-diagMsg = diagnosticMessage-#endif--assertParseFailWith :: HasCallStack => [PsMessage] -> String -> TestType-assertParseFailWith expected source = withFrozenCallStack $-  assertFailWith expected . fmap pm_parsed_source =<< parseGhc source+{-# LANGUAGE CPP #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE LambdaCase #-}
+#if MIN_VERSION_ghc(9,6,0)
+{-# LANGUAGE ScopedTypeVariables #-}
+#endif
+{-# LANGUAGE NoFieldSelectors #-}
+{-# LANGUAGE OverloadedRecordDot #-}
+{-# LANGUAGE ViewPatterns #-}
+
+module MonadicBang.Test.Utils where
+
+import Control.Monad
+import Data.Foldable
+import Data.Function
+
+import Control.Monad.Trans.Writer.CPS
+
+import GHC.Stack
+
+import GHC
+import GHC.Driver.Errors.Types
+import GHC.Types.Error
+import GHC.Types.SourceError
+import GHC.Utils.Outputable hiding ((<>))
+
+import MonadicBang.Test.Utils.RunGhcParser
+import MonadicBang.Internal.Utils
+import Data.Monoid
+
+data FailType
+  = forall a . Show a => IncorrectResult { expectedValue :: a, actualValue :: a }
+  | forall a . Outputable a => Didn'tFail { expectedFails :: [PsMessage], actualValue :: a }
+  | FailedIncorrectly { expectedFails :: [PsMessage], actualFails :: [GhcMessage] }
+
+data Fail = MkFail { error :: FailType, callStack :: CallStack }
+
+type TestType = WriterT (DList Fail) IO ()
+
+type Test = HasCallStack => TestType
+
+runSuite :: TestType -> IO [Fail]
+runSuite test = fromDList <$> execWriterT test
+
+prettyFail :: Fail -> SDoc
+prettyFail failure = vcat
+  [ case failure.error of
+      IncorrectResult{ expectedValue, actualValue } -> vcat
+        [ text "Expected: " <+> text (show expectedValue)
+        , text "but got:  " <+> text (show actualValue)
+        ]
+      Didn'tFail{ expectedFails } -> vcat
+        [ text "Expected failure with"
+        , nest 2 $ diagnosticsSDoc expectedFails
+        , text "but execution succeeded"
+        ]
+      FailedIncorrectly{ expectedFails, actualFails } -> vcat
+        [ text "Expected failure with"
+        , nest 2 $ diagnosticsSDoc expectedFails
+        , text "but execution failed with these errors instead:"
+        , nest 2 $ diagnosticsSDoc actualFails
+        ]
+  , text "at" <+> text (prettyCallStack failure.callStack)
+  ]
+  where
+    diagnosticsSDoc diags = vcat (map (vcat . unDecorated . diagMsg) diags)
+
+recordFail :: HasCallStack => FailType -> TestType
+recordFail err = tell . Endo . (:) $ MkFail err callStack
+
+assertEq :: (HasCallStack, Show a, Eq a) => a -> a -> TestType
+assertEq expected actual = when (expected /= actual) $
+  withFrozenCallStack $ recordFail $ IncorrectResult expected actual
+
+sdocEq :: SDoc -> SDoc -> Bool
+sdocEq = (==) `on` showSDocUnsafe
+
+assertFailWith :: (HasCallStack, Outputable a) => [PsMessage] -> Either SourceError a -> TestType
+assertFailWith expected = \case
+  Right result -> withFrozenCallStack $ recordFail $ Didn'tFail expected result
+  Left err -> unless sameFails do
+    withFrozenCallStack $ recordFail $ FailedIncorrectly expected errMsgs
+    where
+      errMsgs = toList (srcErrorMessages err)
+      toPsMessage = \case
+        GhcPsMessage m -> Just m
+        _ -> Nothing
+      listEq eq xs ys = and $ zipWith eq xs ys
+      sameFails = maybe False (((listEq . listEq) sdocEq `on` map (unDecorated . diagMsg)) expected) $ traverse toPsMessage errMsgs
+
+diagMsg :: forall a . Diagnostic a => a -> DecoratedSDoc
+#if MIN_VERSION_ghc(9,6,0)
+diagMsg = diagnosticMessage (defaultDiagnosticOpts @a)
+#else
+diagMsg = diagnosticMessage
+#endif
+
+assertParseFailWith :: HasCallStack => [PsMessage] -> String -> TestType
+assertParseFailWith expected source = withFrozenCallStack $
+  assertFailWith expected . fmap pm_parsed_source =<< parseGhc source
test/MonadicBang/Test/Utils/QualifiedDo.hs view
@@ -1,13 +1,13 @@-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE PolyKinds #-}-{-# LANGUAGE TypeFamilies #-}--module MonadicBang.Test.Utils.QualifiedDo where--import Prelude (Int, (+), const)--(>>=) :: Int -> (Int -> Int) -> Int-a >>= f = a + f a--(>>) :: Int -> Int -> Int-a >> b = a >>= const b+{-# LANGUAGE ConstraintKinds #-}
+{-# LANGUAGE PolyKinds #-}
+{-# LANGUAGE TypeFamilies #-}
+
+module MonadicBang.Test.Utils.QualifiedDo where
+
+import Prelude (Int, (+), const)
+
+(>>=) :: Int -> (Int -> Int) -> Int
+a >>= f = a + f a
+
+(>>) :: Int -> Int -> Int
+a >> b = a >>= const b
test/MonadicBang/Test/Utils/RunGhcParser.hs view
@@ -1,85 +1,87 @@-{-# LANGUAGE NamedFieldPuns #-}-{-# LANGUAGE BlockArguments #-}-{-# LANGUAGE CPP #-}--{-# OPTIONS -fplugin=MonadicBang #-}---- | This module makes it possible to run GHC's Parser with plugins on source--- files, and check what (if any) errors it produced-module MonadicBang.Test.Utils.RunGhcParser where--import Control.Monad.IO.Class-import Control.Monad.Trans.Except-import Data.Foldable--import GHC-import GHC.Driver.Plugins-import GHC.Driver.Env.Types-import GHC.Driver.Config.Finder-import GHC.Driver.Session-import GHC.LanguageExtensions qualified as LangExt-import GHC.Data.EnumSet qualified as ES-import GHC.Data.StringBuffer-import GHC.Settings.IO-import GHC.Types.SourceFile-import GHC.Types.SourceError-import GHC.Unit.Types-import GHC.Unit.Finder-import GHC.Utils.Fingerprint--import GHC.Paths qualified--import MonadicBang qualified---- | Parses a module-parseGhc :: MonadIO m => String -> m (Either SourceError ParsedModule)-parseGhc src = do-  let dflags = !initialDynFlags-      modNameStr = "MonadicBang.Test.Tmp"-      modName = mkModuleName modNameStr-      modSummary = ModSummary-        { ms_mod = mkModule (stringToUnit modNameStr) modName-        , ms_hsc_src = HsSrcFile-        , ms_location = mkHomeModLocation (initFinderOpts dflags) modName ""-        , ms_hs_hash = fingerprintString src-        , ms_obj_date = Nothing-        , ms_dyn_obj_date = Nothing-        , ms_iface_date = Nothing-        , ms_hie_date = Nothing-        , ms_srcimps = []-        , ms_textual_imps = []-        , ms_ghc_prim_import = False-        , ms_parsed_mod = Nothing-        , ms_hspp_file = modNameStr-        , ms_hspp_opts = dflags-        , ms_hspp_buf = Just $ stringToStringBuffer src-        }-  runDefaultGhc dflags . handleSourceError (pure . Left) $-    Right <$> parseModule modSummary--runDefaultGhc :: MonadIO m => DynFlags -> Ghc a -> m a-runDefaultGhc dflags action = liftIO do-  runGhc (Just GHC.Paths.libdir) (do setSessionDynFlags dflags >> addPlugin >> action)-  where-    addPlugin = do-      let session = !getSession-          plugins = hsc_plugins session-      setSession (session{hsc_plugins = plugins{staticPlugins = StaticPlugin (PluginWithArgs MonadicBang.plugin []) : staticPlugins plugins}})--initialDynFlags :: MonadIO m => m DynFlags-initialDynFlags = do-  dflags <- withExts-  pure $ dflags{generalFlags = ES.insert Opt_ImplicitImportQualified $ generalFlags dflags}-  where-#if MIN_VERSION_ghc(9,6,0)-    withExts = do pure $ foldl' xopt_set (defaultDynFlags !settings') $ exts-#else-    withExts = do pure $ foldl' xopt_set (defaultDynFlags !settings' $ error "llvmConfig") $ exts-#endif-    exts = [LangExt.LambdaCase]--settings' :: MonadIO m => m Settings-settings' = either (error . showSettingsError) id <$> runExceptT (initSettings GHC.Paths.libdir)-  where-    showSettingsError (SettingsError_MissingData s) = s-    showSettingsError (SettingsError_BadData s) = s+{-# LANGUAGE NamedFieldPuns #-}
+{-# LANGUAGE BlockArguments #-}
+{-# LANGUAGE CPP #-}
+
+{-# OPTIONS -fplugin=MonadicBang #-}
+
+-- | This module makes it possible to run GHC's Parser with plugins on source
+-- files, and check what (if any) errors it produced
+module MonadicBang.Test.Utils.RunGhcParser where
+
+import Control.Monad.IO.Class
+import Control.Monad.Trans.Except
+#if !MIN_VERSION_ghc(9,10,0)
+import Data.Foldable
+#endif
+
+import GHC
+import GHC.Driver.Plugins
+import GHC.Driver.Env.Types
+import GHC.Driver.Config.Finder
+import GHC.Driver.Session
+import GHC.LanguageExtensions qualified as LangExt
+import GHC.Data.EnumSet qualified as ES
+import GHC.Data.StringBuffer
+import GHC.Settings.IO
+import GHC.Types.SourceFile
+import GHC.Types.SourceError
+import GHC.Unit.Types
+import GHC.Unit.Finder
+import GHC.Utils.Fingerprint
+
+import GHC.Paths qualified
+
+import MonadicBang qualified
+
+-- | Parses a module
+parseGhc :: MonadIO m => String -> m (Either SourceError ParsedModule)
+parseGhc src = do
+  let dflags = !initialDynFlags
+      modNameStr = "MonadicBang.Test.Tmp"
+      modName = mkModuleName modNameStr
+      modSummary = ModSummary
+        { ms_mod = mkModule (stringToUnit modNameStr) modName
+        , ms_hsc_src = HsSrcFile
+        , ms_location = mkHomeModLocation (initFinderOpts dflags) modName ""
+        , ms_hs_hash = fingerprintString src
+        , ms_obj_date = Nothing
+        , ms_dyn_obj_date = Nothing
+        , ms_iface_date = Nothing
+        , ms_hie_date = Nothing
+        , ms_srcimps = []
+        , ms_textual_imps = []
+        , ms_ghc_prim_import = False
+        , ms_parsed_mod = Nothing
+        , ms_hspp_file = modNameStr
+        , ms_hspp_opts = dflags
+        , ms_hspp_buf = Just $ stringToStringBuffer src
+        }
+  runDefaultGhc dflags . handleSourceError (pure . Left) $
+    Right <$> parseModule modSummary
+
+runDefaultGhc :: MonadIO m => DynFlags -> Ghc a -> m a
+runDefaultGhc dflags action = liftIO do
+  runGhc (Just GHC.Paths.libdir) (do setSessionDynFlags dflags >> addPlugin >> action)
+  where
+    addPlugin = do
+      let session = !getSession
+          plugins = hsc_plugins session
+      setSession (session{hsc_plugins = plugins{staticPlugins = StaticPlugin (PluginWithArgs MonadicBang.plugin []) : staticPlugins plugins}})
+
+initialDynFlags :: MonadIO m => m DynFlags
+initialDynFlags = do
+  dflags <- withExts
+  pure $ dflags{generalFlags = ES.insert Opt_ImplicitImportQualified $ generalFlags dflags}
+  where
+#if MIN_VERSION_ghc(9,6,0)
+    withExts = do pure $ foldl' xopt_set (defaultDynFlags !settings') $ exts
+#else
+    withExts = do pure $ foldl' xopt_set (defaultDynFlags !settings' $ error "llvmConfig") $ exts
+#endif
+    exts = [LangExt.LambdaCase]
+
+settings' :: MonadIO m => m Settings
+settings' = either (error . showSettingsError) id <$> runExceptT (initSettings GHC.Paths.libdir)
+  where
+    showSettingsError (SettingsError_MissingData s) = s
+    showSettingsError (SettingsError_BadData s) = s