Skip to content

Add Plinth parsed-AST deriving plugin to plutus-tx-plugin#7818

Draft
Icelandjack wants to merge 13 commits into
IntersectMBO:masterfrom
Icelandjack:baldurb/plinth-deriving-plugin
Draft

Add Plinth parsed-AST deriving plugin to plutus-tx-plugin#7818
Icelandjack wants to merge 13 commits into
IntersectMBO:masterfrom
Icelandjack:baldurb/plinth-deriving-plugin

Conversation

@Icelandjack

Copy link
Copy Markdown

What

Ports the PlinthPlugin parsed-result plugin from the ghc-plinth "standalone
Plinth" effort into plutus-tx-plugin. It expands data declarations written
with deriving … via PlinthPlugin into:

  • AsData pattern synonyms (a newtype over BuiltinData plus bidirectional patterns)
  • Optics prisms
  • Match destructor functions

It runs at parsedResultAction, alongside the existing core Plinth.Plugin.
The deriving sentinel lives in PlinthPlugin.Via (data Via = PlinthPlugin).

Notes / open items

  • Builds on GHC 9.6 and 9.12 via CPP shims (9.12 verified locally; 9.6 relies on CI here).
  • No activation/golden test yet: the modules compile, but nothing loads the plugin in plutus (in ghc-plinth it is baked in as a static plugin). A test-frontend-plugin-style golden test is the natural follow-up.
  • Licensing: the code originated as Evoke (MIT); needs an attribution decision relative to plutus's Apache-2.0.

🤖 Generated with Claude Code

Ports the PlinthPlugin parsed-result plugin (from the ghc-plinth standalone
effort) into plutus-tx-plugin. It expands data declarations written with
`deriving … via PlinthPlugin` into AsData pattern synonyms, Optics prisms,
and Match functions at parse time, alongside the existing core Plinth.Plugin.

The modules carry CPP shims so they build on both GHC 9.6 and 9.12 (the
versions plutus supports). PlinthPlugin and PlinthPlugin.Via (the
`data Via = PlinthPlugin` sentinel) are exposed; the rest are internal. The
version banner now reads Paths_plutus_tx_plugin.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Icelandjack Icelandjack marked this pull request as draft June 16, 2026 12:22
Icelandjack and others added 5 commits June 16, 2026 14:17
The vendored plinth-plugin suppressed these warnings in its own cabal;
plutus-tx-plugin builds with -Wall -Werror, so: remove unused imports
(CPP-guarding Bag, still needed by the 9.6 makeLHsDecl branch), give the
Config/Flag derivings explicit 'stock' strategies, suppress -Wx-partial on
the three generators (the head calls are non-empty by construction), and
drop the unwired FromData stub.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-Wx-partial does not exist before GHC 9.8, so the bare -Wno-x-partial
pragma tripped -Werror=unrecognised-warning-flags on the 9.6 build. Pair it
with -Wno-unrecognised-warning-flags so the unknown flag is a no-op on 9.6.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
On GHC 9.6, UserTyVar/KindedTyVar already exhaust HsTyVarBndr, so the
catch-all tripped -Werror=overlapping-patterns; move it into the >=9.10
branch (where it covers HsBndrWildCard). That leaves PlinthPlugin.Hsc unused
on 9.6, so CPP-guard its import too.

Verified locally with plutus's -Wall -Werror set on both GHC 9.6.7 and 9.12.2.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds AsData.Spec to frontend-plugin-tests: a module compiled with
-fplugin PlinthPlugin that derives 'AsData via PlinthPlugin' and uses the
generated Circle/Rectangle pattern synonyms. If the plugin does not fire, the
deriving clause survives to the renamer and the module fails to compile, so
compilation itself proves the plugin works; the HUnit case additionally
exercises the generated synonyms at runtime.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The AsData-generated code uses head/tail and emits pattern synonyms
without signatures, tripping -Werror=x-partial and
-Werror=missing-pattern-synonym-signatures. These are inherent to the plugin
output, so suppress them on the test module (the plugin firing is confirmed by
the generated code compiling at all).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
-- This function is responsible for analyzing a deriving strategy to determine
-- if the plugin should fire or not.
parseDerivingStrategy ::
Maybe (Ghc.LDerivStrategy Ghc.GhcPs) -> Maybe [String]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't it return a Bool?

Comment thread plutus-tx-plugin/src/PlinthPlugin.hs Outdated
Comment on lines +49 to +59
flags <- Options.parse Flag.options commandLineOptions srcSpan
let config = Config.fromFlags flags

Monad.when (Config.help config)
. Hsc.throwError srcSpan
. Ghc.vcat
. fmap Ghc.text
. lines
$ Console.usageInfo ("PlinthPlugin version " <> version) Flag.options
Monad.when (Config.version config) . Hsc.throwError srcSpan $
Ghc.text version

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this command-line stuff?

It adds quite a lot of complexity (Paths_ module, Flag, Config) and I'm not sure why?

Comment on lines +2 to +4
-- The plugin-generated code uses 'head'/'tail' internally and emits pattern
-- synonyms without top-level signatures; suppress those warnings here (the
-- -Wno-unrecognised-warning-flags keeps -Wno-x-partial harmless pre-GHC 9.8).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we generate explicit pattern matches instead of head/tail?

Could we generate the top-level signatures too?

Comment thread plutus-tx-plugin/src/PlinthPlugin.hs Outdated
Comment on lines +33 to +38
plugin :: Ghc.Plugin
plugin =
Ghc.defaultPlugin
{ Ghc.parsedResultAction = parsedResultAction,
Ghc.pluginRecompile = Ghc.purePlugin
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we would merge this plugin's features into plutus-tx-plugin directly so that users of plutus-tx-plugin automatically benefit from it. Is it possible?

Ghc.HsTyVar _ _ x -> Just $ Ghc.unLoc x
_ -> Nothing
case Ghc.occNameString $ Ghc.rdrNameOcc rdrName of
"PlinthPlugin" -> Just []

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"PlinthPlugin" -> Just []
"Plinth" -> Just []

Users don't care that it is a plugin or not.

Comment thread plutus-tx-plugin/plutus-tx-plugin.cabal Outdated
Comment on lines +67 to +79
PlinthPlugin.Constant.Module
PlinthPlugin.Generator.AsData
PlinthPlugin.Generator.Common
PlinthPlugin.Generator.Match
PlinthPlugin.Generator.Optics
PlinthPlugin.Hs
PlinthPlugin.Hsc
PlinthPlugin.Options
PlinthPlugin.Type.Config
PlinthPlugin.Type.Constructor
PlinthPlugin.Type.Field
PlinthPlugin.Type.Flag
PlinthPlugin.Type.Type

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd move these into PlutusTx.Plugin.Deriving.* or something like this

Icelandjack and others added 7 commits June 18, 2026 14:17
…ugin.Deriving

Per review (hsyl20): instead of shipping a standalone PlinthPlugin that users
must enable with a second -fplugin, wire the parsed-AST deriving pass into the
existing Plinth.Plugin as its parsedResultAction. Any module compiled with the
Plinth plugin now gets 'deriving via Plinth' automatically.

Also: move the modules from PlinthPlugin.* to PlutusTx.Plugin.Deriving.*, and
rename the deriving-via sentinel to a bare 'data Plinth' (PlutusTx.Plugin.Deriving.Via)
so it reads 'deriving AsData via Plinth' with no DataKinds. The standalone
'plugin' value and its CLI/help/version handling are dropped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ions

Addresses two review comments:
- parseDerivingStrategy returned Maybe [String] but the options channel is
  always empty now, so it becomes isPlinthVia :: ... -> Bool, and the [String]
  options are removed from the Generator type and the handle* plumbing.
- The CLI/help/version handling pulled in the Paths_ module plus Config, Flag
  and Options modules for no real benefit. The pass always uses defaults now,
  so those three modules are deleted and the config threading removed; verbose
  dumping is driven solely by -ddump-deriv.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per review: emit 'pattern Con :: t0 -> ... -> T a b' signatures alongside the
generated pattern synonyms, so user modules no longer need
-Wno-missing-pattern-synonym-signatures (dropped from the activation test).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Per review: the generated view function now binds the constructor's
arguments with an explicit list pattern

  \case args_ of { [a0, a1, ...] -> Just (decode a0, decode a1, ...); _ -> Nothing }

instead of indexing with head/(tail^n). The generated code is now total, so
the AsData activation test drops -Wno-x-partial entirely.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…test

The Match destructor no longer uses head/(tail^n) to index the constructor's
arguments; it binds them with an explicit list pattern

  \case args_ of { [a0, a1, ...] -> f_ (decode a0) (decode a1) ...; _ -> PlutusTx.Builtins.error () }

The wildcard branch is unreachable for well-formed Data and uses the on-chain
error builtin, matching the previous head/tail crash-on-malformed behaviour.

Adds Match/Spec.hs (deriving (AsData, Match) via Plinth) which exercises
matchShape's dispatch + field decoding at runtime, so the codegen is verified
end-to-end in CI like the AsData test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
matchShape's result type variable was generated from a value-namespace name
(makeRandomVariable), so implicit quantification in the signature did not bind
it and GHC reported 'not in scope'. Rebuild it as a type-variable-namespace
name. (Latent bug surfaced by the new Match activation test.)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
No code change. The previous run was fully green except a flaky failure in
plutus-benchmark's uplc-evaluator-integration-tests, whose evaluator service
exited with code -15 (SIGTERM) — unrelated to this PR.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants