Add Plinth parsed-AST deriving plugin to plutus-tx-plugin#7818
Add Plinth parsed-AST deriving plugin to plutus-tx-plugin#7818Icelandjack wants to merge 13 commits into
Conversation
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>
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] |
There was a problem hiding this comment.
Shouldn't it return a Bool?
| 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 |
There was a problem hiding this comment.
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?
| -- 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). |
There was a problem hiding this comment.
Could we generate explicit pattern matches instead of head/tail?
Could we generate the top-level signatures too?
| plugin :: Ghc.Plugin | ||
| plugin = | ||
| Ghc.defaultPlugin | ||
| { Ghc.parsedResultAction = parsedResultAction, | ||
| Ghc.pluginRecompile = Ghc.purePlugin | ||
| } |
There was a problem hiding this comment.
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 [] |
There was a problem hiding this comment.
| "PlinthPlugin" -> Just [] | |
| "Plinth" -> Just [] |
Users don't care that it is a plugin or not.
| 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 |
There was a problem hiding this comment.
I'd move these into PlutusTx.Plugin.Deriving.* or something like this
…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>
What
Ports the
PlinthPluginparsed-result plugin from the ghc-plinth "standalonePlinth" effort into
plutus-tx-plugin. It expands data declarations writtenwith
deriving … via PlinthPlugininto:AsDatapattern synonyms (a newtype overBuiltinDataplus bidirectional patterns)OpticsprismsMatchdestructor functionsIt runs at
parsedResultAction, alongside the existing corePlinth.Plugin.The deriving sentinel lives in
PlinthPlugin.Via(data Via = PlinthPlugin).Notes / open items
test-frontend-plugin-style golden test is the natural follow-up.🤖 Generated with Claude Code