RFC: shutdown action of typechecker plugins
Dear GHC devs, and typechecker plugin authors in particular, In GHC ticket #26935 https://gitlab.haskell.org/ghc/ghc/-/issues/26395, I noticed that typechecker plugins were not getting run when doing pattern match checking, which was a long standing source of frustration. I fixed this in GHC MR !14797 https://gitlab.haskell.org/ghc/ghc/-/merge_requests/14797 (only present in GHC HEAD at the moment), but, as noted in GHC ticket #26839 https://gitlab.haskell.org/ghc/ghc/-/issues/26839, this meant that GHC is repeatedly initialising typechecker plugins for a single module (starting and stopping the plugins for each call of initTcDsForSolver by the pattern-match checker). I am proposing to change the TcPlugin datatype to split the existing "stop" action into two separate actions, "end of typechecking" and "plugin shutdown". This would allow typechecker plugins to be kept running after typechecking (for the benefit of the pattern-match checker) while upholding the invariant that typechecker plugins are initialised once per module being typechecked (modulo small caveats about certain one-off runs of the typechecker by e.g. Backpack). The current TcPlugin data type is as follows: data TcPlugin = forall s. TcPlugin { tcPluginInit :: TcPluginM s , tcPluginSolve :: s -> TcPluginSolver , tcPluginRewrite :: s -> UniqFM TyCon TcPluginRewriter , tcPluginStop :: s -> TcPluginM () -- ^ Clean up after the plugin, when exiting the type-checker. } To allow typechecker plugins to be run by the pattern-match checker, I propose to change this to: data TcPlugin = forall s. TcPlugin { tcPluginInit :: TcPluginM s , tcPluginSolve :: s -> TcPluginSolver , tcPluginRewrite :: s -> UniqFM TyCon TcPluginRewriter , tcPluginPostTc :: s -> TcPluginM () -- ^ Action to run at the end of type-checking. , tcPluginShutdown :: s -> IO () -- ^ Clean up after the plugin, after desugaring is finished. } tcPluginPostTc would run at the end of typechecking. This gives plugins a final opportunity to modify the typechecker environment before it is finalised (as e.g. the clippy plugin does https://github.com/ArturGajowy/ghc-clippy-plugin/blob/0f97274f238b78e1117b75...). If the plugin relies on an active session (e.g. with Z3), the post-tc action should not end the session, because the plugin might still be invoked by the pattern match checker, later, during desugaring. Some remarks: - I changed tcPluginStop to tcPluginPostTc, because it now serves a different function (an action at the end of typechecking that should *not* shut down the plugin). Keeping the same name would be confusing, I think. - The tcPluginShutdown action runs in IO only, as running in TcPluginM would require holding on to a typechecker environment during desugaring, which can cause memory leaks. - This same change would also be carried over to defaulting plugins, but not to hole fit plugins – the latter cannot affect pattern match checking. - A better solution to all of this would be to run the pattern match checker during typechecking, but that would be a significant infrastructural change to GHC. I would like feedback from authors of typechecker plugins on this proposed change. Thanks, Sam
Hi Sam, As a plugin writer, I'm fine with the proposed API change, as long as the final implementation comes with some nice soft documentation: what kind of actions I would do in `tcPluginPostTc` and what kinds of actions I would do in `tcPluginShutdown`. Cheers, Christiaan On Mon, 9 Feb 2026 at 10:46, Sam Derbyshire via ghc-devs < ghc-devs@haskell.org> wrote:
Dear GHC devs, and typechecker plugin authors in particular,
In GHC ticket #26935 https://gitlab.haskell.org/ghc/ghc/-/issues/26395, I noticed that typechecker plugins were not getting run when doing pattern match checking, which was a long standing source of frustration. I fixed this in GHC MR !14797 https://gitlab.haskell.org/ghc/ghc/-/merge_requests/14797 (only present in GHC HEAD at the moment), but, as noted in GHC ticket #26839 https://gitlab.haskell.org/ghc/ghc/-/issues/26839, this meant that GHC is repeatedly initialising typechecker plugins for a single module (starting and stopping the plugins for each call of initTcDsForSolver by the pattern-match checker).
I am proposing to change the TcPlugin datatype to split the existing "stop" action into two separate actions, "end of typechecking" and "plugin shutdown". This would allow typechecker plugins to be kept running after typechecking (for the benefit of the pattern-match checker) while upholding the invariant that typechecker plugins are initialised once per module being typechecked (modulo small caveats about certain one-off runs of the typechecker by e.g. Backpack).
The current TcPlugin data type is as follows:
data TcPlugin = forall s. TcPlugin { tcPluginInit :: TcPluginM s , tcPluginSolve :: s -> TcPluginSolver , tcPluginRewrite :: s -> UniqFM TyCon TcPluginRewriter , tcPluginStop :: s -> TcPluginM () -- ^ Clean up after the plugin, when exiting the type-checker. }
To allow typechecker plugins to be run by the pattern-match checker, I propose to change this to:
data TcPlugin = forall s. TcPlugin { tcPluginInit :: TcPluginM s , tcPluginSolve :: s -> TcPluginSolver , tcPluginRewrite :: s -> UniqFM TyCon TcPluginRewriter , tcPluginPostTc :: s -> TcPluginM () -- ^ Action to run at the end of type-checking. , tcPluginShutdown :: s -> IO () -- ^ Clean up after the plugin, after desugaring is finished. }
tcPluginPostTc would run at the end of typechecking. This gives plugins a final opportunity to modify the typechecker environment before it is finalised (as e.g. the clippy plugin does https://github.com/ArturGajowy/ghc-clippy-plugin/blob/0f97274f238b78e1117b75...). If the plugin relies on an active session (e.g. with Z3), the post-tc action should not end the session, because the plugin might still be invoked by the pattern match checker, later, during desugaring.
Some remarks:
- I changed tcPluginStop to tcPluginPostTc, because it now serves a different function (an action at the end of typechecking that should *not* shut down the plugin). Keeping the same name would be confusing, I think. - The tcPluginShutdown action runs in IO only, as running in TcPluginM would require holding on to a typechecker environment during desugaring, which can cause memory leaks. - This same change would also be carried over to defaulting plugins, but not to hole fit plugins – the latter cannot affect pattern match checking. - A better solution to all of this would be to run the pattern match checker during typechecking, but that would be a significant infrastructural change to GHC.
I would like feedback from authors of typechecker plugins on this proposed change.
Thanks,
Sam _______________________________________________ ghc-devs mailing list -- ghc-devs@haskell.org To unsubscribe send an email to ghc-devs-leave@haskell.org
participants (2)
-
Christiaan Baaij -
Sam Derbyshire