Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test GHC plugins?

Tags:

haskell

ghc

Recent versions of GHC have a new "plugins" feature, where you can write ordinary Haskell code, compile it as usual, and then insert it into the compiler so it can fiddle with GHC's internal state.

Which is very cool. However, there's a small snag: In order to do this, the plugin has to be already compiled (seems obvious) and registered as a package in the package DB!

That's fine once the plugin is finished; package it up and put it on Hackage for all to enjoy. But how do you get around this while trying to develop the package? How does your edit-compile-execute cycle work when at every edit, you have to manually deregister the old package, build the new one, and register it?

Basically, is there some way I can side-step this requirement while trying to develop a plugin?

like image 555
MathematicalOrchid Avatar asked Apr 27 '19 09:04

MathematicalOrchid


Video Answer


1 Answers

If you're using Cabal, it should manage everything for you:

my-plugin.cabal
cabal-version: 2.4
name: my-plugin
version: 1.0.0.0

library
  build-depends: base ^>= 4.12.0.0
               , ghc ^>= 8.6.1
  hs-source-dirs: src
  exposed-modules: MyPlugin

-- could also be an internal library, executable, etc
test-suite test-plugin
  type: exitcode-stdio-1.0
  -- the dependency on my-plugin is everything, placing it
  -- in the package DB of the GHC compiling this test
  build-depends: base, my-plugin
  hs-source-dirs: test
  ghc-options: -fplugin=MyPlugin
  main-is: Main.hs
src/MyPlugin.hs
module MyPlugin(plugin) where
import GhcPlugins
-- this is an example plugin from the manual
-- it prints the names of the non-recursive bindings in each module

plugin :: Plugin
plugin = defaultPlugin {
  installCoreToDos = install
  }

install :: [CommandLineOption] -> [CoreToDo] -> CoreM [CoreToDo]
install _ todo = do
  return (CoreDoPluginPass "Say name" pass : todo)

pass :: ModGuts -> CoreM ModGuts
pass guts = do dflags <- getDynFlags
               bindsOnlyPass (mapM (printBind dflags)) guts
  where printBind :: DynFlags -> CoreBind -> CoreM CoreBind
        printBind dflags bndr@(NonRec b _) = do
          putMsgS $ "Non-recursive binding named " ++ showSDoc dflags (ppr b)
          return bndr
        printBind _ bndr = return bndr
test/Main.hs
module Main where
import Numeric.Natural
import Prelude hiding (even, odd)

-- printed
x :: Int
x = 5

-- not printed
fixObvious :: (a -> a) -> a
fixObvious f = f (fixObvious f)

-- printed
fixSubtle :: (a -> a) -> a
fixSubtle f = let x = f x in x

-- neither printed
even, odd :: Natural -> Bool
even 0 = True
even n = odd (n - 1)
odd 0 = False
odd n = even (n - 1)

-- printed
main :: IO ()
main = return ()
-- if the plugin were more interesting, you may want to test that any
-- modified definitions act like you expect them to
like image 51
HTNW Avatar answered Nov 16 '22 03:11

HTNW