packages feed

monadic-bang 0.2.2.2 → 0.2.2.3

raw patch · 17 files changed

+1776/−1757 lines, 17 filesdep +filepathdep ~basedep ~containersdep ~ghcPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: filepath

Dependency ranges changed: base, containers, ghc, ghc-boot

API changes (from Hackage documentation)

- MonadicBang.Internal: instance GHC.Base.Monoid MonadicBang.Internal.InScope
- MonadicBang.Internal: instance GHC.Base.Monoid MonadicBang.Internal.Occs
- MonadicBang.Internal: instance GHC.Base.Semigroup MonadicBang.Internal.InScope
- MonadicBang.Internal: instance GHC.Base.Semigroup MonadicBang.Internal.Occs
- MonadicBang.Internal: instance GHC.Classes.Eq MonadicBang.Internal.Loc
- MonadicBang.Internal: instance GHC.Classes.Ord MonadicBang.Internal.Loc
- MonadicBang.Internal: instance GHC.Show.Show MonadicBang.Internal.Loc
- MonadicBang.Internal.Effect.Offer: instance (Control.Algebra.Algebra sig m, GHC.Classes.Ord k) => Control.Algebra.Algebra (MonadicBang.Internal.Effect.Offer.Offer k v Control.Effect.Sum.:+: sig) (MonadicBang.Internal.Effect.Offer.OfferC k v m)
- MonadicBang.Internal.Effect.Offer: instance GHC.Base.Functor m => GHC.Base.Functor (MonadicBang.Internal.Effect.Offer.OfferC k v m)
- MonadicBang.Internal.Effect.Offer: instance GHC.Base.Monad m => GHC.Base.Applicative (MonadicBang.Internal.Effect.Offer.OfferC k v m)
- MonadicBang.Internal.Effect.Offer: instance GHC.Base.Monad m => GHC.Base.Monad (MonadicBang.Internal.Effect.Offer.OfferC k v m)
- MonadicBang.Internal.Effect.Uniques: instance GHC.Base.Functor m => GHC.Base.Functor (MonadicBang.Internal.Effect.Uniques.UniquesC m)
- MonadicBang.Internal.Effect.Uniques: instance GHC.Base.Monad m => GHC.Base.Applicative (MonadicBang.Internal.Effect.Uniques.UniquesC m)
- MonadicBang.Internal.Effect.Uniques: instance GHC.Base.Monad m => GHC.Base.Monad (MonadicBang.Internal.Effect.Uniques.UniquesC m)
- MonadicBang.Internal.Effect.Writer.Discard: instance (GHC.Base.Monoid w, Control.Algebra.Algebra sig m) => Control.Algebra.Algebra (Control.Effect.Writer.Internal.Writer w Control.Effect.Sum.:+: sig) (MonadicBang.Internal.Effect.Writer.Discard.DiscardC w m)
- MonadicBang.Internal.Effect.Writer.Discard: instance forall k (w :: k) (m :: * -> *). GHC.Base.Applicative m => GHC.Base.Applicative (MonadicBang.Internal.Effect.Writer.Discard.DiscardC w m)
- MonadicBang.Internal.Effect.Writer.Discard: instance forall k (w :: k) (m :: * -> *). GHC.Base.Functor m => GHC.Base.Functor (MonadicBang.Internal.Effect.Writer.Discard.DiscardC w m)
- MonadicBang.Internal.Effect.Writer.Discard: instance forall k (w :: k) (m :: * -> *). GHC.Base.Monad m => GHC.Base.Monad (MonadicBang.Internal.Effect.Writer.Discard.DiscardC w m)
+ MonadicBang.Internal: instance GHC.Internal.Base.Monoid MonadicBang.Internal.InScope
+ MonadicBang.Internal: instance GHC.Internal.Base.Monoid MonadicBang.Internal.Occs
+ MonadicBang.Internal: instance GHC.Internal.Base.Semigroup MonadicBang.Internal.InScope
+ MonadicBang.Internal: instance GHC.Internal.Base.Semigroup MonadicBang.Internal.Occs
+ MonadicBang.Internal: instance GHC.Internal.Classes.Eq MonadicBang.Internal.Loc
+ MonadicBang.Internal: instance GHC.Internal.Classes.Ord MonadicBang.Internal.Loc
+ MonadicBang.Internal: instance GHC.Internal.Show.Show MonadicBang.Internal.Loc
+ MonadicBang.Internal.Effect.Offer: instance (Control.Algebra.Algebra sig m, GHC.Internal.Classes.Ord k) => Control.Algebra.Algebra (MonadicBang.Internal.Effect.Offer.Offer k v Control.Effect.Sum.:+: sig) (MonadicBang.Internal.Effect.Offer.OfferC k v m)
+ MonadicBang.Internal.Effect.Offer: instance GHC.Internal.Base.Functor m => GHC.Internal.Base.Functor (MonadicBang.Internal.Effect.Offer.OfferC k v m)
+ MonadicBang.Internal.Effect.Offer: instance GHC.Internal.Base.Monad m => GHC.Internal.Base.Applicative (MonadicBang.Internal.Effect.Offer.OfferC k v m)
+ MonadicBang.Internal.Effect.Offer: instance GHC.Internal.Base.Monad m => GHC.Internal.Base.Monad (MonadicBang.Internal.Effect.Offer.OfferC k v m)
+ MonadicBang.Internal.Effect.Uniques: instance GHC.Internal.Base.Functor m => GHC.Internal.Base.Functor (MonadicBang.Internal.Effect.Uniques.UniquesC m)
+ MonadicBang.Internal.Effect.Uniques: instance GHC.Internal.Base.Monad m => GHC.Internal.Base.Applicative (MonadicBang.Internal.Effect.Uniques.UniquesC m)
+ MonadicBang.Internal.Effect.Uniques: instance GHC.Internal.Base.Monad m => GHC.Internal.Base.Monad (MonadicBang.Internal.Effect.Uniques.UniquesC m)
+ MonadicBang.Internal.Effect.Writer.Discard: instance (GHC.Internal.Base.Monoid w, Control.Algebra.Algebra sig m) => Control.Algebra.Algebra (Control.Effect.Writer.Internal.Writer w Control.Effect.Sum.:+: sig) (MonadicBang.Internal.Effect.Writer.Discard.DiscardC w m)
+ MonadicBang.Internal.Effect.Writer.Discard: instance forall k (w :: k) (m :: * -> *). GHC.Internal.Base.Applicative m => GHC.Internal.Base.Applicative (MonadicBang.Internal.Effect.Writer.Discard.DiscardC w m)
+ MonadicBang.Internal.Effect.Writer.Discard: instance forall k (w :: k) (m :: * -> *). GHC.Internal.Base.Functor m => GHC.Internal.Base.Functor (MonadicBang.Internal.Effect.Writer.Discard.DiscardC w m)
+ MonadicBang.Internal.Effect.Writer.Discard: instance forall k (w :: k) (m :: * -> *). GHC.Internal.Base.Monad m => GHC.Internal.Base.Monad (MonadicBang.Internal.Effect.Writer.Discard.DiscardC w m)

Files

CHANGELOG.md view
@@ -1,26 +1,30 @@-# Revision history for monadic-bang
-
-## 0.2.2.1 -- 2024-05-20
-
-* Only run plugin on modules that contain bangs
-
-## 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
+# Revision history for monadic-bang++## 0.2.2.3 -- 2026-02-04++* Added support for GHC 9.14++## 0.2.2.1 -- 2024-05-20++* Only run plugin on modules that contain bangs++## 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,130 +1,132 @@-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.2
-
--- 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
-                    GHC == 9.12.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.22,
-                      ghc >=9.4 && <9.13,
-                      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.13,
-                      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.3++-- 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+                    GHC == 9.12.1+                    GHC == 9.14.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.23,+                      ghc >=9.4 && <9.15,+                      containers ^>=0.6.4.1 || ^>=0.7 || ^>=0.8,+                      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.15,+                      ghc-paths ^>=0.1.0.12,+                      transformers,+                      filepath,+                      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,468 +1,472 @@-{-# 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 _ orig@(ParsedResult (HsParsedModule mod' files) msgs)
-  | null fills = pure orig
-  | otherwise = 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
+{-# 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 _ orig@(ParsedResult (HsParsedModule mod' files) msgs)+  | null fills = pure orig+  | otherwise = 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+#if MIN_VERSION_ghc(9,14,0)+      HsHole _ -> yoink loc >>= maybe (pure e) \lexpr -> do+#else+      HsUnboundVar _ _ -> yoink loc >>= maybe (pure e) \lexpr -> do+#endif+        -- 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,92 +1,101 @@-{-# 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 (error "monadic-bang (test suite): no home path")
-        , 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
-        { spPlugin = PluginWithArgs MonadicBang.plugin []
-#if MIN_VERSION_ghc(9,12,0)
-        , spInitialised = False
-#endif
-        } : 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+#if MIN_VERSION_ghc(9,14,0)+import System.OsPath (unsafeEncodeUtf)+#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+#if MIN_VERSION_ghc(9,14,0)+        , ms_location = mkHomeModLocation (initFinderOpts dflags) modName (error "monadic-bang (test suite): no home path") (unsafeEncodeUtf "hs") HsSrcFile+#else+        , ms_location = mkHomeModLocation (initFinderOpts dflags) modName (error "monadic-bang (test suite): no home path")+#endif+        , 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 = []+#if !MIN_VERSION_ghc(9,14,0)+        , ms_ghc_prim_import = False+#endif+        , 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+        { spPlugin = PluginWithArgs MonadicBang.plugin []+#if MIN_VERSION_ghc(9,12,0)+        , spInitialised = False+#endif+        } : 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