stock (empty) → 0.1.0.0
raw patch · 33 files changed
+8536/−0 lines, 33 filesdep +basedep +ghcdep +inspection-testing
Dependencies added: base, ghc, inspection-testing, stock, transformers
Files
- CHANGELOG.md +38/−0
- LICENSE +29/−0
- README.md +183/−0
- bench/Bench.hs +87/−0
- bench/Configs.hs +68/−0
- examples/Main.hs +62/−0
- examples/QualOverride.hs +32/−0
- inspection/Inspection.hs +207/−0
- plugin/Stock.hs +488/−0
- plugin/Stock/Applicative.hs +188/−0
- plugin/Stock/Bifunctor.hs +992/−0
- plugin/Stock/Bounded.hs +35/−0
- plugin/Stock/Classes1.hs +436/−0
- plugin/Stock/Compat.hs +44/−0
- plugin/Stock/Derive.hs +369/−0
- plugin/Stock/Enum.hs +343/−0
- plugin/Stock/Eq.hs +154/−0
- plugin/Stock/Functor.hs +323/−0
- plugin/Stock/Generic.hs +387/−0
- plugin/Stock/Internal.hs +1463/−0
- plugin/Stock/Ord.hs +230/−0
- plugin/Stock/Override.hs +143/−0
- plugin/Stock/Read.hs +92/−0
- plugin/Stock/Semigroup.hs +85/−0
- plugin/Stock/Show.hs +164/−0
- plugin/Stock/Surface.hs +164/−0
- plugin/Stock/TestEquality.hs +122/−0
- plugin/Stock/Trans.hs +88/−0
- plugin/Stock/Traversable.hs +166/−0
- src/Stock/Type.hs +34/−0
- stock.cabal +196/−0
- test/Spec.hs +1076/−0
- test/Twin.hs +48/−0
+ CHANGELOG.md view
@@ -0,0 +1,38 @@+# Changelog++## 0.1.0.0++Initial release.++* A GHC type-checker plugin that synthesizes class instances for the+ `Stock` / `Stock1` / `Stock2` newtype wrappers, used through `DerivingVia`+ — no `Generic`, no hand-written boilerplate.+* Built-in classes — `Stock`: `Eq`, `Ord`, `Show`, `Read`, `Semigroup`,+ `Monoid`, `Enum`, `Bounded`, `Ix`, `Generic`; `Stock1`: `Functor`,+ `Contravariant`, `Foldable`, `Applicative`, `Generic1`, `Eq1`, `Ord1`,+ `Show1`, `Read1`, `Traversable`, `TestEquality`, `TestCoercion`;+ `Stock2`: `Bifunctor`, `Bifoldable`,+ `Eq2`, `Ord2`, `Show2`, `Read2`, `Category`, `Bitraversable`.+ `Traversable`/`Bitraversable` are synthesized at the wrapper and used+ directly or via the one-liner `traverse g = fmap unStock1 . traverse g+ . Stock1` (a bare `deriving via` can't coerce them onto your type — the+ result `f (t b)` puts the wrapper under an abstract applicative).+* Extensible: satellite packages add new classes with no configuration+ change, via `DeriveStock` instances on the `Stock.Derive` SDK.+* Per-field deriving modifiers via `Stock.Override`: `deriving C via Stock+ (Override T cfg)` (or the type-first synonym `Overriding T cfg`) rewrites+ individual fields' types during synthesis (per-field `DerivingVia`,+ zero-cost). Fields are addressed by name, type, or position (`At`); each+ modifier is pinned (`Sum Int`) or broadcast to the field's own type (`Sum`).+ The same `-fplugin Stock` also lowers a lowercase surface —+ `Override T [ x via Sum, C at 0 via Product ]` — to that marker form at+ parse time.+* Synthesized instances verified against GHC's stock-derived twins and+ benchmarked to identical performance; all evidence passes `-dcore-lint`.+ `Eq`/`Ord`/`Enum`/`Functor`/`Bounded`/`Foldable` optimise to+ byte-identical Core (machine-checked with `inspection-testing`);+ `Traversable`/`Bitraversable` are byte-identical to the natural+ hand-written definition. `Read` (and `Read1`) build `readPrec` as GHC's+ derived `Read` does, so they are byte-faithful including the order of+ ambiguous infix parses.+* Tested on GHC 9.6, 9.8, 9.10, 9.12 and 9.14 (`stock-deepseq`: 9.8+).
+ LICENSE view
@@ -0,0 +1,29 @@+Copyright (c) 2026, Baldur Blondal++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++ * Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.++ * Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.++ * Neither the name of Baldur Blondal nor the names of other+ contributors may be used to endorse or promote products derived+ from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE+ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE+LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN+CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)+ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE+POSSIBILITY OF SUCH DAMAGE.
+ README.md view
@@ -0,0 +1,183 @@+# stock++Constraint solving plugin that enables extensible, composable deriving+without `GHC.Generics`.++It enables deriving through a `Stock(1,2)` newtype that the plugin+generates instances for at compile-time: `Cls (Stock A)`. This+synthesises class instances directly in GHC Core, same as+hand-written, with no `Generic` deriving.++The classes each wrapper synthesises:+++ `Stock`: `Eq`, `Ord`, `Show`, `Read`, `Semigroup`, `Monoid`, `Enum`, `Bounded`, `Ix`, `Generic` (that's right)++ `Stock1`: `Functor` / `Contravariant`, `Foldable`, `Applicative`, `Generic1`, `Eq1`, `Ord1`, `Show1`, `Read1`, `Traversable`†++ `Stock2`: `Bifunctor`, `Bifoldable`, `Eq2`, `Ord2`, `Show2`, `Read2`, `Category`, `Bitraversable`†++(`unpack` / unboxed-strict fields are rejected, with a clear error)++† `Traversable` / `Bitraversable` are synthesized at the wrapper (`Stock1+F` / `Stock2 P`) and usable directly, or put on your own type with a+one-liner:++```haskell+instance Traversable F where+ traverse g = fmap unStock1 . traverse g . Stock1+```++A bare `deriving via Stock1 F` can't reach them: `traverse`'s result `f+(t b)` puts the wrapper under an *abstract* applicative `f`, and+coercing under an abstract `f` (nominal role) is unsound — the same wall+that stops `GeneralizedNewtypeDeriving`. The instance itself is perfectly+ordinary (it's what GHC's own `DeriveTraversable` builds), so the+one-liner — which re-wraps with a real `fmap`, not a coercion — gives you+the instance, and it honours `Override1` / `Override2` (which+`deriving stock Traversable` can't).++```haskell+{-# options_ghc -fplugin Stock #-}++{-# language DerivingVia #-}++import Stock++data Colour = Red | Green | Blue+ deriving (Eq, Ord, Show, Read, Enum, Bounded, Ix) via + Stock Colour++data Tree a = Leaf | Node (Tree a) a (Tree a)+ deriving (Eq, Ord, Show) via + Stock (Tree a)++data Trio a = Trio Int a [a]+ deriving (Functor, Foldable) via + Stock1 Trio+```++The plugin must be enabled (`-fplugin Stock`, in `ghc-options` or a+per-file `options_ghc`). ++## How it works++For a wanted `Cls (Stock T)` the plugin unwraps the newtype with its+coercion, matches `T`'s constructors and builds the _Cls_-class+dictionary directly as Core, requesting each field's own instance as a+fresh wanted (GHC solves `Eq Int` etc. itself). It's direct synthesis+of the wrapped constraint, not delegation.++## Per-field modifiers++```haskell+newtype Override a config = Override a+```++`Override` reshapes individual fields during synthesis by specifying+their behaviour (zero-cost).++```haskell+data One = One { x :: Int, y :: Int }+ deriving (Semigroup, Monoid)+ via Overriding One+ [ x via Sum, y via Product ]+```++combines `x` additively and `y` multiplicatively.++There are a few methods of addressing a field, the plugin allows minor+notational conveniences.+++ `x via F` (`"x" := F`), modifies the field with the `F` wrapper++ `Int via F` (`Int := F`), modifies every `Int` field++ `Con at 0 via F` (`At Con 0 := F`), modifies field 0 of constructor++ `'Con --> F`, a path: modifies every field of `Con`; `'Con --> 0 --> F`+ only its field 0. Each non-terminal hop is a constructor, a position+ (`Nat`) or a label (`Symbol`); the terminal hop is the modifier++ `'[ [F, _] ]` (`'[ [F, Keep] ]`), modifies field 0 only, `_` keeps field++Each modifier is either particular via field `F Int`, a constructor to+modify `F` or a blank `_` (or `Keep`) which leaves a field untouched.+A function-typed modifier needs no parentheses: `x via a -> f b` reads as+`x := (a -> f b)`, since `via` binds looser than `->`.++**The surface plugin.** The lowercase, quote-free surface (`x via Sum`,+`Con at 0`, the `_` blank) is lowered to the honest marker form the+solver reads (`"x" := Sum`, `At Con 0`, `Keep`) by the same `-fplugin+Stock` at parse time (`Stock.Surface`). The generated markers (`:=`,+`At`, `Keep`) are qualified to match however you imported+`Stock.Override`, so `import Stock.Override qualified as O` with+`O.Override … '[ … via Sum, _ ]` resolves too.++### Higher-order++`Override1` / `Override2` lift the same idea over type constructors+instead of types.++```haskell+data Zip a = Zip [a]+ deriving + (Functor, Applicative, Foldable) + via+ Overriding1 Zip '[ '[ZipList] ]+```++## Adding a class++A companion package introduces a new class _Cls_ for synthesis by+writing a `DeriveStock Cls` instance.++The `-fplugin Stock` plugin discovers it: looks up the instance, loads+the deriver with GHC's own plugin loader, and runs it for `deriving+Cls via Stock T`.++```haskell+instance DeriveStock Semigroup where + deriveStock :: Deriver+ deriveStock = Deriver \cls datatype -> do+ let (<+>) = head (classMethods cls)+ a <- fresh (dtVia datatype) "a"+ b <- fresh (dtVia datatype) "b"+ body <- fromProduct datatype (dtVia datatype) (Var a) \xs -> -- (match) a = C x..+ fromProduct datatype (dtVia datatype) (Var b) \ys -> -- (match) b = C y..+ toProduct datatype <$> czipFields cls -- (build) C (x <> y)..+ (\ft d x y -> mkApps (Var (<+>)) [Type ft, d, x, y]) (productCon datatype) xs ys+ EvExpr <$> classDictWith cls (dtVia datatype) [] [(0, mkLams [a, b] body)]+```++The deriver must live in a different module from where it's used. The+plugin loads it as *compiled* code, same-module instances won't+work. A normal dependency (separate package, or just a separate module+built with `-dynamic-too`) works.++## Performance++`cabal bench bench` runs identical workloads against a type defined three ways:+`via Stock`, GHC's stock `deriving`, and hand-written. All three give matching+checksums and run within noise of each other — verified by rebuilding and+re-running on GHC 9.8 – 9.14:++```+Ord: sort 100000 3-field records via Stock 0.075s stock 0.074s hand 0.073s+Functor: fmap (+1) x50 over 100000 via Stock 0.136s stock 0.136s hand 0.137s+```++## Conclusions / realizations++Using _inspection-testing_ shows that the code we generate is+byte-identical to stock (`==-`). `Eq`, `Ord`, `Enum`, `Functor`+optimise to the *same Core* as GHC's own `deriving` on a twin type.++`Read` (and `Read1`) build `readPrec` exactly as GHC's derived `Read`+does — the same `ReadPrec` combinators — and let `readsPrec` come from+the class default, so the result is byte-faithful *including* the order+of ambiguous infix parses. A parity harness (`test/Twin.hs`) checks the+full `readsPrec` output against GHC's own derived `Read` on a+name-identical twin, over valid / whitespace / parenthesised / negative+/ garbage inputs at several precedences.++## Acknowledgments++Developed with substantial assistance from Claude (Anthropic).++## License++BSD-3-Clause.
+ bench/Bench.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE BangPatterns #-}+{-# OPTIONS_GHC -Wno-orphans #-}++-- | A like-for-like benchmark: the same datatype defined three ways — instances+-- synthesized by the plugin (@via Stock@), GHC's stock @deriving@, and+-- hand-written — running identical workloads. The expectation is that all+-- three are within noise of each other (the plugin synthesizes the same+-- operations, so the optimized Core is essentially identical).+module Main (main) where++import qualified Stock as Stock+import Data.List (sort, foldl')+import System.CPUTime (getCPUTime)+import System.Mem (performGC)+import System.Environment (getArgs)+import Control.Exception (evaluate)+import Text.Printf (printf)+import System.IO (hSetBuffering, stdout, BufferMode(LineBuffering))++----------------------------------------------------------------------+-- A 3-field record, three ways (for Eq/Ord)+----------------------------------------------------------------------++data RV = RV Int Int Int deriving (Eq, Ord) via Stock.Stock RV -- plugin+data RS = RS Int Int Int deriving (Eq, Ord) -- stock+data RH = RH Int Int Int -- hand-written+instance Eq RH where RH a b c == RH x y z = a == x && b == y && c == z+instance Ord RH where compare (RH a b c) (RH x y z) =+ compare a x <> compare b y <> compare c z++----------------------------------------------------------------------+-- A parameterised type, three ways (for Functor)+----------------------------------------------------------------------++data FV a = FV a a a deriving Functor via Stock.Stock1 FV -- plugin+data FS a = FS a a a deriving Functor -- stock+data FH a = FH a a a -- hand-written+instance Functor FH where fmap f (FH a b c) = FH (f a) (f b) (f c)++----------------------------------------------------------------------++n :: Int+n = 100000++-- strict sum (avoid building a giant thunk)+ssum :: [Int] -> Int+ssum = foldl' (+) 0++-- Time a strict Int-producing workload, best-of-5 with a full GC before each+-- run. @work@ must be a function of a dummy argument so each repetition+-- rebuilds its thunk from scratch (otherwise the result is shared and only the+-- first run does any work). Best-of-N + per-run GC removes the ordering/GC+-- artefacts that otherwise inflate whichever workload happens to run first.+time :: String -> (Int -> Int) -> IO ()+time label work = do+ -- @d0@ is a genuinely runtime-unknown 0 (no args ⇒ length [] = 0); adding the+ -- repetition index gives each run a distinct argument, so GHC can neither+ -- constant-fold the workload nor share it across the 5 repetitions.+ d0 <- length <$> getArgs+ let once i = do performGC+ t0 <- getCPUTime+ !r <- evaluate (work (d0 + i))+ t1 <- getCPUTime+ pure (fromIntegral (t1 - t0) / (1e12 :: Double), r)+ samples <- mapM once [0 .. 4]+ let best = minimum (map fst samples)+ r = case samples of (s:_) -> snd s; [] -> 0+ printf " %-12s %.3f s (checksum %d)\n" label (best :: Double) r++main :: IO ()+main = do+ hSetBuffering stdout LineBuffering+ -- @d@ is always 0 at run time, but since it is a function argument GHC cannot+ -- treat the workload as a constant and share it — each repetition rebuilds.+ let seeds d = [ (i `mod` 97, i `mod` 31, i + d) | i <- [1 .. n] ]+ putStrLn ("Ord: sort " ++ show n ++ " 3-field records, then checksum")+ time "via Stock" (\d -> ssum [ a + b + c | RV a b c <- sort [ RV x y z | (x,y,z) <- seeds d ] ])+ time "stock" (\d -> ssum [ a + b + c | RS a b c <- sort [ RS x y z | (x,y,z) <- seeds d ] ])+ time "handwritten" (\d -> ssum [ a + b + c | RH a b c <- sort [ RH x y z | (x,y,z) <- seeds d ] ])+ let reps = 50 :: Int -- compose fmap enough times to be measurable+ bump g = iterate (fmap (+1)) g !! reps+ putStrLn ("Functor: fmap (+1) x" ++ show reps ++ " over " ++ show n ++ " values, then checksum")+ time "via Stock" (\d -> ssum [ a + b + c | FV a b c <- map bump [ FV x y z | (x,y,z) <- seeds d ] ])+ time "stock" (\d -> ssum [ a + b + c | FS a b c <- map bump [ FS x y z | (x,y,z) <- seeds d ] ])+ time "handwritten" (\d -> ssum [ a + b + c | FH a b c <- map bump [ FH x y z | (x,y,z) <- seeds d ] ])
+ bench/Configs.hs view
@@ -0,0 +1,68 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE DeriveGeneric #-}+{-# LANGUAGE UndecidableInstances #-}+{-# OPTIONS_GHC -fplugin Stock #-}+-- | The same @Semigroup@ instance reached FOUR ways, on a 2-list product.+-- All must agree (same code); we time each. The point of the project: the+-- @via Stock@ route is a /faster Generically/ — static synthesis, no @Rep@.+module Main (main) where++import qualified Stock as Stock+import GHC.Generics (Generic, Generically(..))+import System.CPUTime (getCPUTime)+import System.Mem (performGC)+import System.Environment (getArgs)+import Control.Exception (evaluate)+import Text.Printf (printf)+import System.IO (hSetBuffering, stdout, BufferMode(LineBuffering))++-- (1) our direct pointwise synthesis+data SVia = SVia [Int] [Int] deriving (Eq, Show)+ deriving Semigroup via Stock.Stock SVia+-- (2) our synthesized Generic, then base's Generically+data SGen = SGen [Int] [Int] deriving (Eq, Show)+ deriving Generic via Stock.Stock SGen+ deriving Semigroup via Generically SGen+-- (3) GHC stock Generic, then base's Generically+data SGenS = SGenS [Int] [Int] deriving (Eq, Show, Generic)+ deriving Semigroup via Generically SGenS+-- (4) hand-written+data SHand = SHand [Int] [Int] deriving (Eq, Show)+instance Semigroup SHand where SHand a b <> SHand x y = SHand (a <> x) (b <> y)++n :: Int+n = 200000++-- fold (<>) over n small products, return a checksum (total element count)+runVia, runGen, runGenS, runHand :: Int -> Int+runVia d = csum (foldr1 (<>) [ SVia [i+d] [i] | i <- [1..n] ]) where csum (SVia a b) = length a + length b+runGen d = csum (foldr1 (<>) [ SGen [i+d] [i] | i <- [1..n] ]) where csum (SGen a b) = length a + length b+runGenS d = csum (foldr1 (<>) [ SGenS [i+d] [i] | i <- [1..n] ]) where csum (SGenS a b) = length a + length b+runHand d = csum (foldr1 (<>) [ SHand [i+d] [i] | i <- [1..n] ]) where csum (SHand a b) = length a + length b++time :: String -> (Int -> Int) -> IO ()+time label work = do+ d0 <- length <$> getArgs+ let once i = do performGC; t0 <- getCPUTime; !r <- evaluate (work (d0+i)); t1 <- getCPUTime+ pure (fromIntegral (t1-t0) / (1e12 :: Double), r)+ ss <- mapM once [0 .. 4]+ let r = case ss of ((_, x) : _) -> x ; [] -> 0+ printf " %-22s %.3f s (checksum %d)\n" label (minimum (map fst ss)) r++-- each config's @(<>)@ on the same inputs, projected to a comparable shape+viaPair, genPair, genSPair, handPair :: ([Int], [Int])+viaPair = case SVia [1,3] [2] <> SVia [4] [5,6] of SVia a b -> (a, b)+genPair = case SGen [1,3] [2] <> SGen [4] [5,6] of SGen a b -> (a, b)+genSPair = case SGenS [1,3] [2] <> SGenS [4] [5,6] of SGenS a b -> (a, b)+handPair = case SHand [1,3] [2] <> SHand [4] [5,6] of SHand a b -> (a, b)++main :: IO ()+main = do+ hSetBuffering stdout LineBuffering+ putStrLn ("all four configs agree: " ++ show+ (all (== handPair) [viaPair, genPair, genSPair] && handPair == ([1,3,4], [2,5,6])))+ putStrLn ("Semigroup <> fold over " ++ show n ++ " products (best-of-5):")+ time "via Stock (direct)" runVia+ time "via Generically (Stock)" runGen+ time "via Generically (stock Gen)" runGenS+ time "hand-written" runHand
+ examples/Main.hs view
@@ -0,0 +1,62 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}+-- record selectors below are illustrative and intentionally unused+{-# OPTIONS_GHC -fplugin Stock -Wno-unused-top-binds #-}++-- | Stock supplies the /structural/ instance; the standard @DerivingVia@+-- modifier newtypes then reshape it. Each type below derives through+-- @Modifier (Stock … )@, so the modifier delegates to the instance the plugin+-- synthesizes:+--+-- * 'Down' — structural 'Ord', reversed.+-- * 'Reverse' — structural 'Foldable', folded back-to-front.+-- * 'Backwards' — the structural 'Applicative', effects sequenced right-to-left.+module Main (main) where++import Stock (Stock(..), Stock1(..))+import Stock.Override (Override(..), type (:=))+import QualOverride (qualCheck)+import Data.Ord (Down(..))+import Data.Monoid (Sum(..), Product(..))+import Data.Functor.Reverse (Reverse(..))+import Control.Applicative.Backwards (Backwards(..))+import Data.Foldable (toList)+import Control.Monad (unless)+import System.Exit (exitFailure)++-- @Ord@ as the reverse of the structural order: @Gold < Silver < Bronze@.+data Medal = Bronze | Silver | Gold+ deriving (Eq, Show) via Stock Medal+ deriving Ord via Down (Stock Medal)++-- @Foldable@ that visits fields back-to-front.+data Triple a = Triple a a a+ deriving Show via Stock (Triple a)+ deriving Foldable via Reverse (Stock1 Triple)++-- The structural (position-wise) @Applicative@, run backwards.+data Two a = Two a a+ deriving (Eq, Show) via Stock (Two a)+ deriving Functor via Stock1 Two+ deriving Applicative via Backwards (Stock1 Two)++-- Per-field @Override@ (lowered from the lowercase surface by the same+-- @-fplugin Stock@): combine @vx@ additively (@Sum@) and @vy@ multiplicatively+-- (@Product@) — a @Semigroup@ you cannot get from plain @Stock V@ without+-- rewriting @V@'s field types.+data V = V { vx :: Int, vy :: Int }+ deriving (Eq, Show) via Stock V+ deriving Semigroup via Stock (Override V [ vx via Sum, vy via Product ])++main :: IO ()+main = do+ let checks =+ [ ("Down reverses Ord", compare Bronze Gold == GT+ && maximum [Bronze, Silver, Gold] == Bronze)+ , ("Reverse reverses fold", toList (Triple 1 2 (3 :: Int)) == [3, 2, 1])+ , ("Backwards Applicative", (Two (+1) (+10) <*> Two 100 (200 :: Int)) == Two 101 210)+ , ("Override per-field <>", V 2 3 <> V 5 7 == V 7 21)+ ] ++ qualCheck+ mapM_ (\(name, ok) -> putStrLn ((if ok then "ok " else "FAIL ") ++ name)) checks+ unless (all snd checks) exitFailure
+ examples/QualOverride.hs view
@@ -0,0 +1,32 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}+{-# OPTIONS_GHC -fplugin Stock -Wno-unused-top-binds #-}++-- | Regression: the @-fplugin Stock@ surface pass must qualify the markers it+-- generates (@:=@, @At@, @Keep@) the /same way @Override@ itself was imported/.+-- Here "Stock.Override" is imported __only qualified__ (as @O@), so an+-- unqualified @Keep@ or @:=@ in the lowered config would be out of scope. The+-- modifiers themselves (@Sum@, @Product@) keep whatever scope the user gave them.+module QualOverride (qualCheck) where++import Stock (Stock(..))+import Stock.Override qualified as O+import Data.Monoid (Sum(..), Product(..))++-- entry surface: @via@ lowers to @O.:=@ (mirrors the @O.Override@ qualifier).+data A = A { ax :: Int, ay :: Int }+ deriving (Eq, Show) via Stock A+ deriving Semigroup via Stock (O.Override A '[ ax via Sum, ay via Product ])++-- positional surface: @_@ lowers to @O.Keep@. Field 0 (@Int@) via @Sum@; field+-- 1 (@[Int]@) kept at its own list 'Semigroup'.+data B = B Int [Int]+ deriving (Eq, Show) via Stock B+ deriving Semigroup via Stock (O.Override B '[ [ Sum, _ ] ])++qualCheck :: [(String, Bool)]+qualCheck =+ [ ("qualified Override, via ⇒ O.:=", A 2 3 <> A 5 7 == A 7 21)+ , ("qualified Override, _ ⇒ O.Keep", B 1 [2] <> B 3 [4] == B 4 [2, 4])+ ]
+ inspection/Inspection.hs view
@@ -0,0 +1,207 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE TemplateHaskell #-}+{-# OPTIONS_GHC -fplugin=Stock -fplugin=Test.Inspection.Plugin #-}++-- | Compile-time zero-cost proof. @inspection-testing@ is a TEST-ONLY+-- dependency; the @stock@ library never depends on it, so users pay nothing.+-- Two complementary pins, each of which FAILS the build if violated:+--+-- (1) Byte-identical to stock (@(==-)@, equality up to types and coercions):+-- the Stock-derived method optimises to the /same Core/ as GHC's own+-- @deriving@ on a twin type. This needs a stock twin and Core that does+-- not mention the datatype's own names, so it fits @Eq@\/@Ord@\/@Enum@\/+-- @Functor@ — pinned byte-identical here.+--+-- (2) Wrapper fully erased ('hasNoType'): for the classes without a usable+-- twin (@Show@; the lifted @Eq1@\/@Ord1@\/@Show1@\/@Foldable@ and+-- @Eq2@\/@Ord2@\/@Show2@\/@Bifoldable@ — GHC has no stock @deriving@ for+-- these, so there is nothing to @(==-)@ against). Trick: wrap the argument+-- with the @Stock@\/@Stock1@\/@Stock2@ constructor so the plugin's own+-- unwrap coercion cancels it (@(Stock t) |> rCo = t@); fully applied, the+-- method worker inlines and NO wrapper type survives. @hasNoType@ proves+-- the newtype and its @coerce@ are gone — i.e. zero cost.+--+-- Pin (2) needs the obligation to /consume/ its result (return @Int@\/@()@\/+-- @String@…) so GHC can't eta-reduce it back to a bare dictionary cast. Given+-- that, even single-value \"producers\" — @<>@, @mempty@, @minBound@\/+-- @maxBound@ — pin fine: wrap the inputs and consume\/@unStock@ the output, and+-- every wrapper cancels. The sole holdout is @Read@ (and @Read1@\/@Read2@): it+-- builds a @[(Stock T, String)]@ through opaque combinators (@readParen@\/@lex@+-- at @Stock T@), so the wrapper is baked into intermediate types that can't be+-- consumed away. @Read@ is covered behaviourally by the @spec@ suite.+module Main (main) where++import Stock+import Test.Inspection+import Data.Functor.Classes (Eq1(..), Ord1(..), Show1(..), Eq2(..), Ord2(..), Show2(..))+import Data.Bifoldable (Bifoldable(..))+import Data.Bitraversable (Bitraversable(..))++-- product: Eq / Ord (name-free Core — twin-comparable)+data P = P Int Bool deriving (Eq, Ord) via Stock P+data P' = P' Int Bool deriving stock (Eq, Ord)++-- Show: pinned by newtype erasure (see below)+data S = MkS Int Bool deriving Show via Stock S+++-- enumeration: Enum+data E = E0 | E1 | E2 deriving Enum via Stock E+data E' = F0 | F1 | F2 deriving stock Enum++-- single-value producers: Semigroup/Monoid (mempty, <>) and Bounded+-- (minBound/maxBound). Unlike Read, the result is one value, so @unStock@+-- cancels the plugin's output wrap pointwise — no wrapper survives.+data Sg = Sg [Int] [Int] deriving (Semigroup, Monoid) via Stock Sg+data Bd = Bd Bool Ordering deriving Bounded via Stock Bd+data Bd' = Bd' Bool Ordering deriving stock Bounded -- twin for the (==-) pin++-- type constructor: Functor (==-) + Foldable/Eq1/Ord1/Show1 (newtype erasure)+data T a = T Int a [a]+ deriving stock (Eq, Ord, Show) -- satisfy the lifted classes' superclasses+ deriving Functor via Stock1 T+ deriving (Foldable, Eq1, Ord1, Show1) via Stock1 T+data T' a = T' Int a [a] deriving stock (Functor, Foldable)++-- two-parameter constructor: Bifoldable/Eq2/Ord2/Show2 (newtype erasure)+data B a b = B a b [b]+ deriving stock (Eq, Ord, Show) -- base of the superclass tower+ deriving (Eq1, Ord1, Show1) via Stock1 (B a) -- Eq2/Ord2/Show2 superclasses+ deriving (Bifoldable, Eq2, Ord2, Show2) via Stock2 B++sEq, tEq :: P -> P -> Bool+sEq = (==)+tEq = \(P a b) (P c d) -> P' a b == P' c d++sCmp, tCmp :: P -> P -> Ordering+sCmp = compare+tCmp = \(P a b) (P c d) -> compare (P' a b) (P' c d)++sFromE, tFromE :: E -> Int+sFromE = fromEnum+tFromE = \e -> fromEnum (toEnum (fromEnum e) :: E') -- structural twin++sFmap, tFmap :: (a -> b) -> T a -> T b+sFmap = fmap+tFmap = \f (T n x xs) -> T n (f x) (map f xs)++-- Bounded: route the twin through case-of-known-con so its cons cancel,+-- leaving identical name-free Core (same trick as Eq/Ord/Enum/Functor).+sMinB, tMinB, sMaxB, tMaxB :: Bd+sMinB = minBound+tMinB = case (minBound :: Bd') of Bd' a b -> Bd a b+sMaxB = maxBound+tMaxB = case (maxBound :: Bd') of Bd' a b -> Bd a b++-- Foldable: toList; twin routed through T' (its cons cancel). Pins the+-- explicitly-synthesized foldr against GHC's stock foldr.+sToList, tToList :: T Int -> [Int]+sToList = foldr (:) []+tToList = \(T n x xs) -> foldr (:) [] (T' n x xs)++-- Traversable via the one-liner, pinned against the natural applicative walk.+sTr, tTr :: (Int -> Maybe Int) -> T Int -> Maybe (T Int)+sTr g = fmap unStock1 . traverse g . Stock1+tTr g = \x -> case x of T n y ys -> pure (T n) <*> g y <*> traverse g ys++-- @Show@ can't be twin-pinned with @(==-)@ (it embeds the constructor name, so+-- a same-named twin must live in another module and its worker is a distinct+-- top-level id). Instead we certify the property that matters for zero cost:+-- /manually/ wrapping the argument with the @Stock@ constructor makes the+-- plugin's own unwrap coercion cancel it — @(Stock t) |> rCo = t@ — and, fully+-- applied (to @""@), the method worker inlines, so NO @Stock@ survives+-- optimisation. 'hasNoType' proves the wrapper and its @coerce@ are erased.+sShow :: Int -> S -> String+sShow d t = showsPrec d (Stock t) "" -- wrap; plugin unwraps; cancels++-- @Read@ is the exception we cannot pin: it /produces/ the value, so+-- @readsPrec@ at @Stock S@ has result type @[(Stock S, String)]@ — @Stock@ is in+-- the parse result itself, not a cancellable input wrapper. So neither+-- @(==-)@ (stock uses @ReadPrec@, the plugin the Report's @ReadS@) nor+-- 'hasNoType' applies; @Read@ is covered behaviourally by the @spec@ suite.++-- Lifted classes (no stock twin to @(==-)@ against): pin them the same way as+-- Show — wrap with the @Stock1@ constructor (the plugin's unwrap cancels it),+-- fully apply, and certify no @Stock1@ survives. All are /consumers/ here.+sFold :: T Int -> Int+sFold t = sum (Stock1 t)++sLiftEq :: T Int -> T Int -> Bool+sLiftEq x y = liftEq (==) (Stock1 x) (Stock1 y)++sLiftCmp :: T Int -> T Int -> Ordering+sLiftCmp x y = liftCompare compare (Stock1 x) (Stock1 y)++sLiftShow :: Int -> T Int -> String+sLiftShow d t = liftShowsPrec showsPrec showList d (Stock1 t) ""++sBifold :: B Int Int -> Int+sBifold x = bifoldr (+) (+) 0 (Stock2 x)++-- @bifoldr@: GHC has no stock @Bifoldable@, so we pin against the natural+-- hand-written /direct/ recursion (the twin routes through @B@'s fields). This+-- passes only because @bifoldr@ is synthesized directly; the @Endo@-based class+-- default would not match.+sBiFr, tBiFr :: (Int -> [Int] -> [Int]) -> (Int -> [Int] -> [Int]) -> [Int] -> B Int Int -> [Int]+sBiFr = bifoldr+tBiFr = \f g z x -> case x of B a b bs -> f a (g b (foldr g z bs))++-- @bitraverse@ via the one-liner (@fmap unStock2 . bitraverse . Stock2@) pinned+-- against the natural hand-written applicative walk @pure Con <*> .. <*> ..@.+-- Verifies the Stock2 wrapper cancels and the structure matches.+sBiTr, tBiTr :: (Int -> Maybe Int) -> (Int -> Maybe Int) -> B Int Int -> Maybe (B Int Int)+sBiTr f g = fmap unStock2 . bitraverse f g . Stock2+tBiTr f g = \x -> case x of B a b bs -> pure B <*> f a <*> g b <*> traverse g bs++sLiftEq2 :: B Int Int -> B Int Int -> Bool+sLiftEq2 x y = liftEq2 (==) (==) (Stock2 x) (Stock2 y)++sLiftCmp2 :: B Int Int -> B Int Int -> Ordering+sLiftCmp2 x y = liftCompare2 compare compare (Stock2 x) (Stock2 y)++sLiftShow2 :: Int -> B Int Int -> String+sLiftShow2 d x = liftShowsPrec2 showsPrec showList showsPrec showList d (Stock2 x) ""++-- single-value producers: wrap inputs and unwrap the result; both cancel.+-- /consume/ the @<>@ result (return Int) so GHC can't eta-reduce to a bare+-- producer dictionary cast (the same reason 'sShow' returns a String): the+-- @<>@ worker inlines, inputs and output wrappers cancel, no Stock survives.+sMappend :: Sg -> Sg -> Int+sMappend x y = case unStock (Stock x <> Stock y) of Sg p q -> sum p + sum q++sMempty :: Sg+sMempty = unStock (mempty :: Stock Sg)++sMinBound :: Bd+sMinBound = unStock (minBound :: Stock Bd)++sMaxBound :: Bd+sMaxBound = unStock (maxBound :: Stock Bd)++inspect $ 'sEq ==- 'tEq+inspect $ 'sCmp ==- 'tCmp+inspect $ 'sFromE ==- 'tFromE+inspect $ 'sFmap ==- 'tFmap+inspect $ 'sMinB ==- 'tMinB+inspect $ 'sMaxB ==- 'tMaxB+inspect $ 'sToList ==- 'tToList+inspect $ 'sTr ==- 'tTr+inspect $ 'sShow `hasNoType` ''Stock+inspect $ 'sFold `hasNoType` ''Stock1+inspect $ 'sLiftEq `hasNoType` ''Stock1+inspect $ 'sLiftCmp `hasNoType` ''Stock1+inspect $ 'sLiftShow `hasNoType` ''Stock1+inspect $ 'sBifold `hasNoType` ''Stock2+inspect $ 'sBiFr ==- 'tBiFr+inspect $ 'sBiTr ==- 'tBiTr+inspect $ 'sLiftEq2 `hasNoType` ''Stock2+inspect $ 'sLiftCmp2 `hasNoType` ''Stock2+inspect $ 'sLiftShow2 `hasNoType` ''Stock2+inspect $ 'sMappend `hasNoType` ''Stock+inspect $ 'sMempty `hasNoType` ''Stock+inspect $ 'sMinBound `hasNoType` ''Stock+inspect $ 'sMaxBound `hasNoType` ''Stock++main :: IO ()+main = putStrLn "ok: Eq/Ord/Enum/Functor/Bounded/Foldable Core-identical (bifoldr = hand-written); Show/Semigroup/Monoid + lifted consumers erase the wrapper"
+ plugin/Stock.hs view
@@ -0,0 +1,488 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE DerivingVia #-}+-- We use a few partial selectors on values whose shape is guaranteed by GHC+-- invariants — @head (classMethods c)@ (a class always has its methods),+-- @head (tyConDataCons tc)@ (guarded non-empty), and the @[lt,eq,gt]@ pattern+-- on @Ordering@'s three constructors — so we silence the corresponding noise.+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns #-}+{-# OPTIONS_GHC -Wno-unused-imports #-}++-- | Synthesize class instances for the @Stock@ \/ @Stock1@ \/+-- @Stock2@ newtype wrappers directly from a datatype's structure,+-- same as hand-written without @Generic@. This one module both+-- provides the wrappers and /is/ the plugin, so a single name does+-- everything:+--+-- > {-# options_ghc -fplugin Stock #-}+-- >+-- > import Stock+-- > +-- > data Colour = Red | Green | Blue +-- > deriving (Eq, Ord, Show) +-- > via Stock Colour+--+-- Supported classes:+--+-- * @Stock@: @Eq@, @Ord@, @Show@, @Read@, @Semigroup@, @Monoid@, @Enum@,+-- @Bounded@, @Ix@, @Generic@.+-- * @Stock1@: @Functor@ \/ @Contravariant@, @Foldable@, @Applicative@,+-- @Generic1@, @Eq1@, @Ord1@, @Show1@, @Read1@, @Traversable@.+-- * @Stock2@: @Bifunctor@, @Bifoldable@, @Eq2@, @Ord2@, @Show2@, @Read2@,+-- @Category@, @Bitraversable@.+--+-- @Traversable@\/@Bitraversable@ are synthesized at the wrapper (@Stock1+-- F@\/@Stock2 P@) and used directly, or put on your type with the one-liner+-- @traverse g = fmap unStock1 . traverse g . Stock1@. A bare @deriving via@+-- can't coerce them onto your type: @traverse@'s result @f (t b)@ places the+-- wrapper under an abstract applicative (nominal role), which is unsound to+-- coerce — but the instance itself is ordinary, so the one-liner works.+--+-- The set is open: a satellite package adds a brand-new class with no+-- configuration change (just a dependency) by writing a @DeriveStock@+-- instance. See "Stock.Derive".+--+-- Individual fields can be reshaped during synthesis (per-field+-- @DerivingVia@) with @deriving Cls via Stock (Override T cfg)@; see+-- "Stock.Override".+--+-- /When does it run?/ All synthesis happens at __compile time__,+-- while the plugin type-checks your @deriving@ clause: it emits+-- ordinary Core — the same a hand-written instance would. At runtime+-- there is no @Rep@, no reflection, no instance lookup; you pay+-- exactly the usual dictionary plumbing (including any dictionaries+-- that polymorphic or polymorphically-recursive code builds at+-- runtime), and never anything extra for having used @Stock@.++module Stock+ ( Stock(..), Stock1(..), Stock2(..), plugin+ -- Re-exported derivable classes that are /not/ already in Prelude, so+ -- @import Stock@ alone suffices for any @deriving C via Stock T@ clause.+ -- (Class names only: the methods live in their home modules, and+ -- re-exporting @Category@'s @id@\/@.@ would clash with Prelude.)+ , Contravariant+ , Eq1, Ord1, Show1, Read1+ , Eq2, Ord2, Show2, Read2+ , Bifunctor, Bifoldable+ , Category+ , Ix+ , Generic, Generic1+ -- The per-field modifier surface, so @import Stock@ alone suffices for+ -- @deriving C via Overriding T '[ field via M, … ]@ (the surface @via@ /+ -- @_@ lower to these @:=@ / @Keep@ markers).+ , module Stock.Override+ ) where++import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Tc.Utils.Monad (addErrTc)+import GHC.Tc.Errors.Types (mkTcRnUnknownMessage)+import GHC.Types.Error (mkPlainError, noHints)+import GHC.Core.Class (Class, className, classMethods, classOpItems, classTyCon)+import GHC.Core.Predicate (classifyPredType, Pred(ClassPred), mkClassPred)+import GHC.Builtin.Types.Prim (intPrimTy)+import GHC.Builtin.PrimOps (PrimOp(TagToEnumOp))+import GHC.Builtin.PrimOps.Ids (primOpId)+import GHC.Builtin.Names ( eqClassName, ordClassName, appendName+ , enumClassName, mapName, numClassName+ , enumFromToName, enumFromThenToName+ , eqStringName+ , genClassName, repTyConName, u1TyConName, k1TyConName+ , prodTyConName, sumTyConName+ , monoidClassName, foldableClassName, functorClassName+ , semigroupClassName )+import Stock.Compat ( gHC_INTERNAL_SHOW, gHC_INTERNAL_READ+ , gHC_INTERNAL_LIST, gHC_INTERNAL_GENERICS )+import GHC.Core.Reduction (mkReduction)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import GHC.Rename.Fixity (lookupFixityRn)+import GHC.Types.Fixity (Fixity(..), defaultFixity)+import GHC.Core.TyCo.Compare (eqType)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.SimpleOpt (defaultSimpleOpts)+import GHC.Core.Unfold.Make (mkInlineUnfoldingWithArity)+import GHC.Core.InstEnv (classInstances, is_dfun, is_tys)+import GHC.Runtime.Loader (getValueSafely)+import Stock.Derive+import Stock.Override+import Data.Maybe (catMaybes, fromJust, isJust, fromMaybe)+import Data.Traversable (for)+import qualified Data.Monoid as Mon (Alt(..))+import Stock.Trans (MaybeT(..))+import Control.Monad (zipWithM, unless, guard)+import Data.IORef (IORef, newIORef, readIORef, modifyIORef')+import Stock.Type (Stock(..), Stock1(..), Stock2(..))+-- Re-exported (class names only) so @import Stock@ covers every derivable class.+import Data.Functor.Contravariant (Contravariant)+import Data.Functor.Classes (Eq1, Ord1, Show1, Read1, Eq2, Ord2, Show2, Read2)+import Data.Bifunctor (Bifunctor)+import Data.Bifoldable (Bifoldable)+import Control.Category (Category)+import Data.Ix (Ix)+import GHC.Generics (Generic, Generic1)+import Stock.Surface (lowerOverrides)+import Stock.Internal+import Stock.Bounded+import Stock.Eq+import Stock.Ord+import Stock.Semigroup+import Stock.Show+import Stock.Enum+import Stock.Read+import Stock.Functor+import Stock.Applicative+import Stock.Traversable (synthTraversable)+import Stock.TestEquality (synthTestEquality, synthTestCoercion)+import Stock.Bifunctor+import Stock.Generic+import Stock.Classes1++-- | The Stock type-checker plugin. Enable with @-fplugin Stock@.+-- +-- > {-# options_ghc -fplugin Stock #-}+plugin :: Plugin+plugin = defaultPlugin+ { tcPlugin = \_ -> Just stockPlugin+ -- same @-fplugin Stock@ also lowers the @Override@ surface sugar at parse time+ , parsedResultAction = \_ _ -> pure . lowerOverrides+ , pluginRecompile = purePlugin+ }++-- | Present a raw @CtLoc -> TcPluginM (EvTerm, [Ct])@ synthesizer (@Ord@,+-- @Show@, @Read@, @Enum@, @Ix@) as a @Deriver@, so every built-in @Stock@+-- class dispatches uniformly through 'runDeriverAttempt' — exactly like the+-- SDK-native ones (@Eq@, @Bounded@, @Semigroup@, …).+-- Each constructor is paired with its per-field override coercions+-- (@realFieldType ~R modifierType@, 'Refl' when not overridden) so the raw+-- synthesizers can honour @Override@ — using the modifier type for the field's+-- instance and coercing the bound value — exactly as 'matchSOP' does for the+-- SDK derivers. 'Refl' everywhere ⇒ byte-identical Core to before.+viaSynth :: (Class -> CtLoc -> Type -> Type -> Coercion -> [(DataCon, [Coercion])] -> TcPluginM (EvTerm, [Ct]))+ -> Deriver+viaSynth f = Deriver \cls dt -> synthTc \loc ->+ f cls loc (dtVia dt) (dtType dt) (dtUnwrap dt)+ (map (\c -> (conDataCon c, conFieldCos c)) (dtCons dt))++stockPlugin :: TcPlugin+stockPlugin = TcPlugin+ { tcPluginInit = do+ seen <- tcPluginIO (newIORef [])+ stock <- lookupTyConMaybe "Stock.Type" "Stock"+ stock1 <- lookupTyConMaybe "Stock.Type" "Stock1"+ stock2 <- lookupTyConMaybe "Stock.Type" "Stock2"+ witCls <- lookupClassMaybe "Stock.Derive" "DeriveStock"+ k1Tc <- tcLookupTyCon k1TyConName+ prodTc <- tcLookupTyCon prodTyConName+ rTc <- lookupOrig gHC_INTERNAL_GENERICS (mkTcOcc "R") >>= tcLookupTyCon+ gen <- GenEnv stock stock1 stock2 witCls+ <$> tcLookupClass genClassName+ <*> tcLookupTyCon repTyConName+ <*> tcLookupTyCon u1TyConName+ <*> pure k1Tc+ <*> pure prodTc <*> pure (head (tyConDataCons prodTc))+ <*> tcLookupTyCon sumTyConName+ <*> lookupMetaEnv+ <*> lookupGen1Env+ <*> pure (mkTyConTy rTc)+ <*> lookupTyConMaybe "Stock.Override" "Override"+ <*> lookupTyConMaybe "Stock.Override" ":="+ <*> lookupTyConMaybe "Stock.Override" "At"+ <*> lookupTyConMaybe "Stock.Override" "Keep"+ <*> lookupTyConMaybe "Stock.Override" "-->"+ <*> lookupClassMaybe "Stock.Derive" "DeriveStock1"+ <*> lookupClassMaybe "Stock.Derive" "DeriveStock2"+ <*> lookupTyConMaybe "Stock.Override" "Override2"+ <*> lookupTyConMaybe "Stock.Override" "Override1"+ pure (PluginState seen gen)+ , tcPluginSolve = solveStock+ , tcPluginRewrite = \st -> listToUFM+ [ (geRepTc (psGen st), rewriteRep (psGen st))+ , (g1RepTc (geGen1 (psGen st)), rewriteRep1 (psGen st)) ]+ , tcPluginStop = \_ -> pure ()+ }++-- | Look up a 'TyCon' by module and name, returning 'Nothing' if the module+-- is not in scope — so the plugin stays inert instead of erroring when our+-- @Stock@ wrapper isn't imported.+solveStock :: PluginState -> EvBindsVar -> [Ct] -> [Ct] -> TcPluginM TcPluginSolveResult+solveStock st _ev _given wanted = do+ results <- for wanted (trySolve st)+ let solutions = catMaybes [ s | (s, _, _) <- results ]+ newWanteds = concat [ w | (_, w, _) <- results ]+ insolubles = concat [ i | (_, _, i) <- results ]+ pure TcPluginSolveResult+ { tcPluginInsolubleCts = insolubles+ , tcPluginSolvedCts = solutions+ , tcPluginNewCts = newWanteds+ }++-- | Result of attempting one constraint: an optional solution, any new wanted+-- constraints to emit, and any constraints we declare insoluble (after+-- reporting a custom error for them).+trySolve :: PluginState -> Ct -> TcPluginM Attempt+trySolve st ct =+ case classifyPredType (ctPred ct) of+ -- unary class over a type/constructor; @clsArgs@ may carry a leading+ -- (invisible) kind argument (poly-kinded classes like @Generic1@), so the+ -- type we act on is the /last/ argument.+ ClassPred cls (reverse -> (wrappedTy : _)) ->+ fromMaybe (Nothing, [], [])+ <$> runSolver (mconcat [stockSolver, stock1Solver, stock2Solver]) st ct cls wrappedTy+ _ -> pure (Nothing, [], [])++-- | @Cls (Stock T)@ — build the dictionary from @T@'s constructors.+stockSolver :: Solver+stockSolver = Solver \st ct cls wrappedTy -> do+ -- @Stock (Override T cfg)@ takes priority; its decode emits per-cell coercion+ -- wanteds (@extraCts@) that ride alongside the deriver's own.+ mOver <- mkOverrideRepr (psGen st) (ctLoc ct) wrappedTy+ case mOver of+ Just (Left err) -> Just <$> notImplemented st ct err+ Just (Right (repr, extraCts)) -> Just . addCts extraCts <$> dispatchStock st ct cls wrappedTy repr+ Nothing -> case mkRepr (geStock (psGen st)) wrappedTy of+ Nothing -> pure Nothing+ Just repr -> Just <$> dispatchStock st ct cls wrappedTy repr++-- | Append extra wanted constraints to a solve attempt.+addCts :: [Ct] -> Attempt -> Attempt+addCts extra (sol, ws, ins) = (sol, extra ++ ws, ins)++-- | Dispatch a recognised @Stock@(-@Override@) representation to the right+-- built-in deriver (or a discovered companion via 'tryWitness').+dispatchStock :: PluginState -> Ct -> Class -> Type -> Repr -> TcPluginM Attempt+dispatchStock st ct cls wrappedTy repr+ | reprUnpacked repr =+ notImplemented st ct $+ text "stock: cannot derive via Stock for a type whose"+ <+> text "constructors have UNPACKed or unboxed strict fields"+ <+> text "(their runtime representation differs from their source type)"+ $$ nest 2 (text "in the derived instance for"+ <+> quotes (ppr (className cls) <+> ppr wrappedTy))+ | otherwise = do+ let innerTy = rInner repr+ co = rCo repr+ case occNameString (nameOccName (className cls)) of+ "Eq" -> runDeriverAttempt eqDeriver ct cls (toDatatype wrappedTy repr)+ "Ord" -> runDeriverAttempt (viaSynth synthOrd) ct cls (toDatatype wrappedTy repr)+ "Show" -> runDeriverAttempt (viaSynth synthShow) ct cls (toDatatype wrappedTy repr)+ "Read" -> runDeriverAttempt (viaSynth synthRead) ct cls (toDatatype wrappedTy repr)+ "Enum"+ | reprIsEnum repr -> runDeriverAttempt (viaSynth synthEnum) ct cls (toDatatype wrappedTy repr)+ | otherwise ->+ notImplemented st ct $+ text "stock: deriving Enum via Stock requires an"+ <+> text "enumeration (constructors without fields)"+ $$ nest 2 (text "in the derived instance for"+ <+> quotes (ppr (className cls) <+> ppr wrappedTy))+ "Ix"+ | reprIsEnum repr -> runDeriverAttempt (viaSynth synthIx) ct cls (toDatatype wrappedTy repr)+ | reprSingleCon repr -> runDeriverAttempt (viaSynth synthIxProduct) ct cls (toDatatype wrappedTy repr)+ | otherwise ->+ notImplemented st ct $+ text "stock: deriving Ix via Stock requires an enumeration"+ <+> text "or a single-constructor product"+ $$ nest 2 (text "in the derived instance for"+ <+> quotes (ppr (className cls) <+> ppr wrappedTy))+ "Bounded"+ | reprIsEnum repr || reprSingleCon repr ->+ runDeriverAttempt boundedDeriver ct cls (toDatatype wrappedTy repr)+ | otherwise ->+ notImplemented st ct $+ text "stock: deriving Bounded via Stock requires an"+ <+> text "enumeration or a single-constructor type"+ $$ nest 2 (text "in the derived instance for"+ <+> quotes (ppr (className cls) <+> ppr wrappedTy))+ "Generic" -> do+ ev <- synthGeneric (psGen st) wrappedTy innerTy co (rCons repr)+ pure (Just (ev, ct), [], [])+ "Semigroup"+ | reprSingleCon repr -> runDeriverAttempt semigroupDeriver ct cls (toDatatype wrappedTy repr)+ | otherwise -> notImplemented st ct $+ text "stock: Semigroup via Stock requires a single-constructor"+ <+> text "(product) type" $$ nest 2 (text "in the derived instance for"+ <+> quotes (ppr (className cls) <+> ppr wrappedTy))+ "Monoid"+ | reprSingleCon repr -> runDeriverAttempt monoidDeriver ct cls (toDatatype wrappedTy repr)+ | otherwise -> notImplemented st ct $+ text "stock: Monoid via Stock requires a single-constructor"+ <+> text "(product) type" $$ nest 2 (text "in the derived instance for"+ <+> quotes (ppr (className cls) <+> ppr wrappedTy))+ other -> do+ -- not a built-in: try a companion-provided @instance DeriveStock Cls@+ mw <- tryWitness st ct cls (toDatatype wrappedTy repr)+ case mw of+ Just attempt -> pure attempt+ Nothing ->+ notImplemented st ct $+ text "stock: deriving" <+> quotes (text other)+ <+> text "via Stock is not supported, and no"+ <+> text "'instance DeriveStock' was found for it"+ $$ nest 2 (text "in the derived instance for"+ <+> quotes (ppr (className cls) <+> ppr wrappedTy))+-- | @Cls (Stock1 F)@ — a class over a (poly-kinded) type constructor.+stock1Solver :: Solver+stock1Solver = Solver \st ct cls wrappedTy ->+ case (geStock1 (psGen st), tyConAppTyCon_maybe wrappedTy) of+ (Just ourTc, Just stTc) | stTc == ourTc+ , [_, f] <- tyConAppArgs wrappedTy+ -- TestEquality/TestCoercion handle GADTs directly (whose constructors+ -- carry coercion fields that trip 'dcUnpacked'); let them through.+ , occNameString (nameOccName (className cls)) `notElem` ["TestEquality", "TestCoercion"]+ , maybe False (any dcUnpacked . tyConDataCons) (tyConAppTyCon_maybe f) ->+ fmap Just $ notImplemented st ct $+ text "stock: cannot derive via Stock1 for a type whose"+ <+> text "constructors have UNPACKed or unboxed strict fields"+ <+> text "(their runtime representation differs from their source type)"+ $$ nest 2 (text "in the derived instance for"+ <+> quotes (ppr (className cls) <+> ppr wrappedTy))+ (Just ourTc, Just stTc) | stTc == ourTc+ , [_, f] <- tyConAppArgs wrappedTy ->+ fmap Just $+ let runStock1 synth = do+ m <- synth (psGen st) cls (ctLoc ct) wrappedTy f+ case m of+ Just (ev, ws) -> pure (Just (ev, ct), ws, [])+ Nothing ->+ notImplemented st ct $+ text "stock: deriving" <+> ppr (className cls)+ <+> text "via Stock1 supports only covariant fields (the"+ <+> text "parameter, constants, or a functor applied to it)"+ $$ nest 2 (text "in the derived instance for"+ <+> quotes (ppr (className cls) <+> ppr wrappedTy))+ in case occNameString (nameOccName (className cls)) of+ "Functor" -> runStock1 synthFunctor+ "Applicative" -> runStock1 synthApplicative+ "Foldable" -> runStock1 synthFoldable+ "Contravariant" -> runStock1 synthContravariant+ "Generic1" -> runStock1 synthGeneric1+ "Eq1" -> runStock1 synthEq1+ "Ord1" -> runStock1 synthOrd1+ "Show1" -> runStock1 synthShow1+ "Read1" -> runStock1 synthRead1+ -- The instance IS synthesized (and usable at @Stock1 F@, or on your+ -- type via @traverse g = fmap unStock1 . traverse g . Stock1@); only+ -- the DerivingVia coercion onto @F@ is impossible — @traverse@'s+ -- result @f (t b)@ puts the wrapper under an abstract applicative+ -- (nominal role), so a bare @deriving via Stock1@ still fails there.+ "Traversable" -> runStock1 synthTraversable+ "TestEquality" -> runStock1 synthTestEquality+ "TestCoercion" -> runStock1 synthTestCoercion+ _ -> do+ -- not a built-in: try a companion @instance DeriveStock1 Cls@+ mw <- tryWitness1 st ct cls wrappedTy f+ case mw of+ Just attempt -> pure attempt+ Nothing -> notImplemented st ct $+ text "stock: deriving" <+> quotes (ppr (className cls))+ <+> text "via Stock1 is not supported, and no"+ <+> text "'instance DeriveStock1' was found for it"+ $$ nest 2 (text "in the derived instance for"+ <+> quotes (ppr (className cls) <+> ppr wrappedTy))+ -- @Cls (Stock1 F a..)@ /fully applied/ (kind Type): Stock1 is a+ -- transparent newtype, so solve from @Cls (F a..)@ and coerce.+ -- This discharges the quantified superclass @forall a. Cls a =>+ -- Cls (Stock1 F a)@ that lifted classes (Eq1, NFData1, Hashable1,+ -- …) carry, straight from the user's own @Cls (F a)@ instance,+ -- for any class.+ (Just ourTc, Just stTc) | stTc == ourTc+ , (_ : f : rest@(_ : _)) <- tyConAppArgs wrappedTy ->+ fmap Just $ do+ -- @f@ may itself be @Override1 cfg realF@; peel it so the sub-wanted+ -- lands on the user's real @Cls (realF a..)@ instance, not the+ -- instance-less @Override1@ wrapper. (One univ coercion spans both hops.)+ let realF = fst (peelOverride1With (ovTcsGen "Override1" (psGen st)) f)+ innerTy = mkAppTys realF rest -- F a..+ -- @Cls (F a..) ~R Cls (Stock1 F a..)@, plugin-asserted: the dicts+ -- share a representation (Stock1 is a newtype). We assert it+ -- directly rather than lift the newtype coercion through the class+ -- TyCon — whose parameter is /nominal/, so a representational arg+ -- coercion there is role-incorrect (-dcore-lint rejects it).+ dictCo = mkStockCo (PluginProv "stock") Representational+ (mkClassPred cls [innerTy]) (mkClassPred cls [wrappedTy])+ ev <- newWanted (ctLoc ct) (mkClassPred cls [innerTy])+ pure (Just (EvExpr (Cast (ctEvExpr ev) dictCo), ct), [mkNonCanonical ev], [])+ _ -> pure Nothing++-- | @Cls (Stock2 P)@ — a class over a (poly-kinded) two-parameter constructor.+stock2Solver :: Solver+stock2Solver = Solver \st ct cls wrappedTy ->+ case (geStock2 (psGen st), tyConAppTyCon_maybe wrappedTy) of+ (Just ourTc, Just stTc) | stTc == ourTc+ , [_, _, p] <- tyConAppArgs wrappedTy+ , maybe False (any dcUnpacked . tyConDataCons) (tyConAppTyCon_maybe p) ->+ fmap Just $ notImplemented st ct $+ text "stock: cannot derive via Stock2 for a type whose"+ <+> text "constructors have UNPACKed or unboxed strict fields"+ $$ nest 2 (text "in the derived instance for"+ <+> quotes (ppr (className cls) <+> ppr wrappedTy))+ (Just ourTc, Just stTc) | stTc == ourTc+ , [_, _, p] <- tyConAppArgs wrappedTy ->+ fmap Just $+ let runStock2 synth = do+ m <- synth (psGen st) cls (ctLoc ct) wrappedTy p+ case m of+ Just (ev, ws) -> pure (Just (ev, ct), ws, [])+ Nothing ->+ notImplemented st ct $+ text "stock: deriving" <+> ppr (className cls)+ <+> text "via Stock2 supports only covariant fields in the last"+ <+> text "two parameters (each parameter, constants, or a functor"+ <+> text "applied to one)"+ $$ nest 2 (text "in the derived instance for"+ <+> quotes (ppr (className cls) <+> ppr wrappedTy))+ in case occNameString (nameOccName (className cls)) of+ "Bifunctor" -> runStock2 synthBifunctor+ "Bifoldable" -> runStock2 synthBifoldable+ "Eq2" -> runStock2 synthEq2+ "Ord2" -> runStock2 synthOrd2+ "Show2" -> runStock2 synthShow2+ "Read2" -> runStock2 synthRead2+ "Category" -> runStock2 synthCategory+ -- synthesized at Stock2 (usable directly / via the one-liner+ -- @bitraverse f g = fmap unStock2 . bitraverse f g . Stock2@); a+ -- bare @deriving via Stock2@ still fails — bitraverse's result+ -- @f (t c d)@ puts the wrapper under an abstract applicative.+ "Bitraversable" -> runStock2 synthBitraversable+ _ -> do+ -- not a built-in: try a companion @instance DeriveStock2 Cls@+ mw <- tryWitness2 st ct cls wrappedTy p+ case mw of+ Just attempt -> pure attempt+ Nothing -> notImplemented st ct $+ text "stock: deriving" <+> quotes (ppr (className cls))+ <+> text "via Stock2 is not supported, and no"+ <+> text "'instance DeriveStock2' was found for it"+ $$ nest 2 (text "in the derived instance for"+ <+> quotes (ppr (className cls) <+> ppr wrappedTy))+ -- @Cls (Stock2 P a..)@ /further applied/: Stock2 is a transparent+ -- newtype, so solve from @Cls (P a..)@ and coerce (discharges the+ -- quantified superclass @forall a. Cls a => Cls1 (Stock2 P a)@ of+ -- bi-lifted classes from the user's own @Cls1 (P a)@ instance).+ (Just ourTc, Just stTc) | stTc == ourTc+ , (_ : _ : p : rest@(_ : _)) <- tyConAppArgs wrappedTy ->+ fmap Just $ do+ -- as in the Stock1 passthrough: peel an @Override2 cfg realP@ wrapper+ -- so the sub-wanted lands on the user's real @Cls (realP a..)@ instance.+ let realP = fst (peelOverride2With (ovTcsGen "Override2" (psGen st)) p)+ innerTy = mkAppTys realP rest -- P a..+ -- as in the Stock1 passthrough: assert @Cls (P a..) ~R+ -- Cls (Stock2 P a..)@ directly (role-correct under -dcore-lint),+ -- rather than lift the newtype coercion through the nominal class param.+ dictCo = mkStockCo (PluginProv "stock") Representational+ (mkClassPred cls [innerTy]) (mkClassPred cls [wrappedTy])+ ev <- newWanted (ctLoc ct) (mkClassPred cls [innerTy])+ pure (Just (EvExpr (Cast (ctEvExpr ev) dictCo), ct), [mkNonCanonical ev], [])+ _ -> pure Nothing++-- | Report a custom error for a constraint and mark it insoluble, so the user+-- sees exactly why synthesis failed instead of a generic "No instance". The+-- message is reported at most once (the solver may retry the same constraint).
+ plugin/Stock/Applicative.hs view
@@ -0,0 +1,188 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE LambdaCase #-}+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns -Wno-unused-imports #-}++-- | Pointwise @Applicative@ via @Stock1@, for single-constructor (product)+-- types — a faster @Generically1@: @pure@ replicates into every field and+-- @(\<*\>)@ applies field-wise. Each field must be the parameter (applied+-- directly), an @Applicative@ functor of it (delegating to that functor), or a+-- constant — which, Const-style (exactly as @Generically1@), is fine given a+-- @Monoid@: @pure@ fills it with @mempty@ and @(\<*\>)@\/@liftA2@ combine with+-- @(\<>)@. (Any sum type is still rejected.) The @Functor@ superclass+-- dictionary comes from 'synthFunctor'.+module Stock.Applicative where++import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Core.Class (Class)+import GHC.Core.Predicate (mkClassPred)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import GHC.Builtin.Names (functorClassName, monoidClassName)+import Control.Monad (forM, zipWithM)+import Stock.Derive (classMethod)+import Stock.Internal+import Stock.Functor (synthFunctor)++-- | How one field of the product is handled by @pure@\/@(\<*\>)@\/@liftA2@: it+-- /is/ the parameter; an @Applicative@ functor @m@ of it (with @m@'s dict, and a+-- @Just@ @h t ~R m t@ coercion builder when reshaped by an @Override1@, else+-- @Nothing@); or a constant of type @ft@ handled Const-style via its @Monoid@.+data FldSpec = FsParam+ | FsApp Type CoreExpr (Maybe (Type -> Coercion))+ | FsConst Type CoreExpr++-- | Coerce a field value /into/ the modifier functor (@h t ~R m t@); identity+-- when the field is not reshaped.+castInOv :: Maybe (Type -> Coercion) -> Type -> CoreExpr -> CoreExpr+castInOv Nothing _ e = e+castInOv (Just coFn) t e = Cast e (coFn t)++-- | Coerce a result /back/ from the modifier functor to the real field type.+castBackOv :: Maybe (Type -> Coercion) -> Type -> CoreExpr -> CoreExpr+castBackOv Nothing _ e = e+castBackOv (Just coFn) t e = Cast e (mkSymCo (coFn t))++synthApplicative :: GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthApplicative gen applicativeCls loc wrappedTy f =+ case geStock1 gen of+ Just st1Tc+ -- peel an optional @Override1 cfg F@ (functor reshape, e.g. @[] -> ZipList@)+ | let (realF, mMods) = peelOverride1 gen f+ , Just fTc <- tyConAppTyCon_maybe realF+ , [dc] <- tyConDataCons fTc -> do -- products only: one constructor+ functorCls <- tcLookupClass functorClassName+ monoidCls <- tcLookupClass monoidClassName+ let fixed = tyConAppArgs realF+ pureSel = classMethod "pure" applicativeCls -- index 0: pure+ apSel = classMethod "<*>" applicativeCls -- index 1: (<*>)+ laSel = classMethod "liftA2" applicativeCls -- index 2: liftA2+ memptySel = classMethod "mempty" monoidCls+ mappendSel= classMethod "mappend" monoidCls+ coAt t = coDown1 gen st1Tc wrappedTy f realF t -- Stock1 (Override1? F) t ~R F t++ -- Classify each field once: parameter, an @Applicative@ functor of it,+ -- or a constant — which (Const-style, as @Generically1@ does) is fine+ -- given a @Monoid@: @pure@ uses @mempty@, @(\<*\>)@ uses @(\<>)@.+ ctv <- freshTyVar "p"+ let ctvTy = mkTyVarTy ctv+ fldTys = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [ctvTy]))+ kinds = map (classifyField ctv ctvTy) fldTys++ -- @FsParam@ | @FsApp h dApplicative@ | @FsConst ft dMonoid@; an arrow or+ -- other unsupported shape still bails with 'Nothing'.+ specsW <- forM (zip3 [0 :: Int ..] kinds fldTys) \(i, k, ft) -> case k of+ Just FParam -> pure (Just (FsParam, []))+ Just (FApp h) -> case override1Mod gen mMods i of+ -- Override1: reshape the field functor @h a -> m a@ (e.g. ZipList),+ -- with a @h t ~R m t@ coercion threaded through pure/<*>/liftA2.+ Just m -> do ev <- newWanted loc (mkClassPred applicativeCls [m])+ let coFn t = mkStockCo (PluginProv "stock") Representational+ (mkAppTy h t) (mkAppTy m t)+ pure (Just (FsApp m (ctEvExpr ev) (Just coFn), [mkNonCanonical ev]))+ Nothing -> do ev <- newWanted loc (mkClassPred applicativeCls [h])+ pure (Just (FsApp h (ctEvExpr ev) Nothing, [mkNonCanonical ev]))+ Just FConst -> do ev <- newWanted loc (mkClassPred monoidCls [ft])+ pure (Just (FsConst ft (ctEvExpr ev), [mkNonCanonical ev]))+ _ -> pure Nothing++ case sequence specsW of+ Nothing -> pure Nothing+ Just sw -> do+ let fieldSpec = map fst sw+ appWs = concatMap snd sw++ -- pure :: forall a. a -> Stock1 F a+ aP <- freshTyVar "a"+ let aPt = mkTyVarTy aP+ xId <- freshId aPt "x"+ let pureVal FsParam = Var xId+ pureVal (FsApp m d mco) = castBackOv mco aPt (mkApps (Var pureSel) [Type m, d, Type aPt, Var xId])+ pureVal (FsConst ft d) = mkApps (Var memptySel) [Type ft, d]+ pureImpl = mkLams [aP, xId] $+ Cast (mkCoreConApps dc (map Type (fixed ++ [aPt]) ++ map pureVal fieldSpec))+ (mkSymCo (coAt aPt))++ -- (<*>) :: forall a b. Stock1 F (a -> b) -> Stock1 F a -> Stock1 F b+ aS <- freshTyVar "a" ; bS <- freshTyVar "b"+ let aSt = mkTyVarTy aS ; bSt = mkTyVarTy bS ; fnTy = mkVisFunTyMany aSt bSt+ sffId <- freshId (mkAppTy wrappedTy fnTy) "sff"+ sfaId <- freshId (mkAppTy wrappedTy aSt) "sfa"+ ffs <- zipWithM (\n t -> freshId t ("ff" ++ show n)) [0 :: Int ..]+ (map scaledThing (dataConInstOrigArgTys dc (fixed ++ [fnTy])))+ xas <- zipWithM (\n t -> freshId t ("xa" ++ show n)) [0 :: Int ..]+ (map scaledThing (dataConInstOrigArgTys dc (fixed ++ [aSt])))+ cbF <- freshId (mkTyConApp fTc (fixed ++ [fnTy])) "cbf"+ cbA <- freshId (mkTyConApp fTc (fixed ++ [aSt])) "cba"+ let apVal FsParam ff xa = App (Var ff) (Var xa)+ apVal (FsApp m d mco) ff xa =+ castBackOv mco bSt (mkApps (Var apSel)+ [ Type m, d, Type aSt, Type bSt+ , castInOv mco fnTy (Var ff), castInOv mco aSt (Var xa) ])+ apVal (FsConst ft d) ff xa = -- combine the constants with (<>)+ mkApps (Var mappendSel) [Type ft, d, Var ff, Var xa]+ apImpl = mkLams [aS, bS, sffId, sfaId] $+ destructInner fTc (fixed ++ [fnTy]) (Cast (Var sffId) (coAt fnTy))+ cbF (mkAppTy wrappedTy bSt)+ [ Alt (DataAlt dc) ffs $+ destructInner fTc (fixed ++ [aSt]) (Cast (Var sfaId) (coAt aSt))+ cbA (mkAppTy wrappedTy bSt)+ [ Alt (DataAlt dc) xas $+ Cast (mkCoreConApps dc (map Type (fixed ++ [bSt])+ ++ zipWith3 apVal fieldSpec ffs xas))+ (mkSymCo (coAt bSt)) ] ]++ -- liftA2 :: forall a b c. (a -> b -> c) -> Stock1 F a -> Stock1 F b -> Stock1 F c+ -- Given DIRECTLY (one structural pass) rather than via the class+ -- default @liftA2 g x = (g \<$\> x) \<*\> y@, which would @fmap@ then+ -- @\<*\>@ (two passes). Each field: @g p q@ for the parameter, or+ -- @liftA2 \@h g p q@ for an Applicative-functor field.+ laA <- freshTyVar "a" ; laB <- freshTyVar "b" ; laC <- freshTyVar "c"+ let laAt = mkTyVarTy laA ; laBt = mkTyVarTy laB ; laCt = mkTyVarTy laC+ gTy = mkVisFunTyMany laAt (mkVisFunTyMany laBt laCt)+ gId <- freshId gTy "g"+ ls1 <- freshId (mkAppTy wrappedTy laAt) "s1"+ ls2 <- freshId (mkAppTy wrappedTy laBt) "s2"+ ps <- zipWithM (\n t -> freshId t ("p" ++ show n)) [0 :: Int ..]+ (map scaledThing (dataConInstOrigArgTys dc (fixed ++ [laAt])))+ qs <- zipWithM (\n t -> freshId t ("q" ++ show n)) [0 :: Int ..]+ (map scaledThing (dataConInstOrigArgTys dc (fixed ++ [laBt])))+ cb1 <- freshId (mkTyConApp fTc (fixed ++ [laAt])) "cb1"+ cb2 <- freshId (mkTyConApp fTc (fixed ++ [laBt])) "cb2"+ let laVal FsParam p q = mkApps (Var gId) [Var p, Var q]+ laVal (FsApp m d mco) p q =+ castBackOv mco laCt (mkApps (Var laSel)+ [ Type m, d, Type laAt, Type laBt, Type laCt, Var gId+ , castInOv mco laAt (Var p), castInOv mco laBt (Var q) ])+ laVal (FsConst ft d) p q = -- constants ignore g, combine with (<>)+ mkApps (Var mappendSel) [Type ft, d, Var p, Var q]+ liftA2Impl = mkLams [laA, laB, laC, gId, ls1, ls2] $+ destructInner fTc (fixed ++ [laAt]) (Cast (Var ls1) (coAt laAt))+ cb1 (mkAppTy wrappedTy laCt)+ [ Alt (DataAlt dc) ps $+ destructInner fTc (fixed ++ [laBt]) (Cast (Var ls2) (coAt laBt))+ cb2 (mkAppTy wrappedTy laCt)+ [ Alt (DataAlt dc) qs $+ Cast (mkCoreConApps dc (map Type (fixed ++ [laCt])+ ++ zipWith3 laVal fieldSpec ps qs))+ (mkSymCo (coAt laCt)) ] ]++ -- the @Functor@ superclass dictionary is the first dict-con field+ synthFunctor gen functorCls loc wrappedTy f >>= \case+ Nothing -> pure Nothing+ Just (fEv, fWs) -> do+ dict <- recDictWith applicativeCls wrappedTy [unwrapEv fEv]+ [(0, pureImpl), (1, apImpl), (2, liftA2Impl)]+ pure (Just (EvExpr dict, fWs ++ appWs))+ _ -> pure Nothing
+ plugin/Stock/Bifunctor.hs view
@@ -0,0 +1,992 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE DerivingVia #-}+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns -Wno-unused-imports #-}+-- | All @Stock2@ synthesizers (classes over a two-parameter type @P@):+--+-- * @Bifunctor@ \/ @Bifoldable@ — map\/fold the last two parameters.+-- * @Eq2@ \/ @Ord2@ \/ @Show2@ \/ @Read2@ — the lifted "two-parameter"+-- 'Data.Functor.Classes' (mirroring "Stock.Classes1" one level up).+-- * @Bitraversable@ — synthesized directly (usable at the wrapper \/ via the+-- one-liner; a bare @deriving via@ can't, abstract-applicative role).+-- * @Category@ — pointwise @id@\/@(.)@ for a single-constructor product+-- whose fields are each a 'Control.Category.Category' in the two params.+--+-- Field shapes: each of the two parameters, constants, or a (covariant) functor+-- applied to one (the flat 'classifyBiField'); @Bifunctor@ also goes through the+-- n-ary variance engine for nested\/self-applied fields like @Either a b@.+module Stock.Bifunctor where+-- Most names below (data-con/type builders, coercion builders, occ-name+-- helpers, …) are re-exported by 'GHC.Plugins', so we only import explicitly+-- the ones it does not provide.+import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Tc.Utils.Monad (addErrTc)+import GHC.Tc.Errors.Types (mkTcRnUnknownMessage)+import GHC.Types.Error (mkPlainError, noHints)+import GHC.Core.Class (Class, className, classMethods, classOpItems, classTyCon, classTyVars, classSCTheta)+import GHC.Core.Predicate (classifyPredType, Pred(ClassPred), mkClassPred)+import GHC.Core.TyCo.Subst (substTy, emptySubst)+import GHC.Builtin.Types (orderingTyCon)+import GHC.Builtin.Types.Prim (intPrimTy)+import GHC.Builtin.PrimOps (PrimOp(TagToEnumOp))+import GHC.Builtin.PrimOps.Ids (primOpId)+import GHC.Builtin.Names ( eqClassName, ordClassName, appendName+ , enumClassName, mapName, numClassName+ , enumFromToName, enumFromThenToName+ , eqStringName+ , genClassName, repTyConName, u1TyConName, k1TyConName+ , prodTyConName, sumTyConName+ , monoidClassName, foldableClassName, functorClassName+ , semigroupClassName, applicativeClassName, traversableClassName )+import Stock.Compat ( gHC_INTERNAL_SHOW, gHC_INTERNAL_READ+ , gHC_INTERNAL_LIST, gHC_INTERNAL_GENERICS )+import GHC.Core.Reduction (mkReduction)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import GHC.Rename.Fixity (lookupFixityRn)+import GHC.Types.Fixity (Fixity(..), defaultFixity)+import GHC.Core.TyCo.Compare (eqType)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.SimpleOpt (defaultSimpleOpts)+import GHC.Core.Unfold.Make (mkInlineUnfoldingWithArity)+import GHC.Core.InstEnv (classInstances, is_dfun, is_tys)+import GHC.Runtime.Loader (getValueSafely)+import Stock.Derive+import Data.Maybe (catMaybes, fromJust, isJust, fromMaybe)+import qualified Data.Monoid as Mon (Alt(..)) -- 'Alt' clashes with GHC.Core's case-alt 'Alt'+import Stock.Trans (MaybeT(..))+import Control.Monad (forM, zipWithM, unless, guard)+import Data.IORef (IORef, newIORef, readIORef, modifyIORef')+import Control.Monad (zipWithM)+import Data.List (zip4, zip5, zipWith4)+import Data.Maybe (listToMaybe)+import Stock.Internal+-- field reshape: 'reshapeCo' (@h t ~R m t@) + 'castReshape' live in "Stock.Internal".++data BiField+ = BFA | BFB -- ^ the field /is/ @a@ resp. @b@+ | BFConst -- ^ mentions neither+ | BFFoldA Type | BFFoldB Type -- ^ @h a@ / @h b@ (covariant, @h@ over one param)++classifyBiField :: TyVar -> TyVar -> Type -> Type -> Type -> Maybe BiField+classifyBiField atv btv aTy bTy ft+ | ft `eqType` aTy = Just BFA+ | ft `eqType` bTy = Just BFB+ | not (inFt atv) && not (inFt btv) = Just BFConst+ | Just (h, larg) <- splitAppTy_maybe ft+ , larg `eqType` bTy, clean h = Just (BFFoldB h)+ | Just (h, larg) <- splitAppTy_maybe ft+ , larg `eqType` aTy, clean h = Just (BFFoldA h)+ | otherwise = Nothing+ where inFt v = v `elemVarSet` tyCoVarsOfType ft+ clean h = not (atv `elemVarSet` tyCoVarsOfType h)+ && not (btv `elemVarSet` tyCoVarsOfType h)++-- | For @Category@: a field must be exactly @h a b@ — a (poly-kinded)+-- two-parameter constructor @h@ applied to /both/ datatype parameters, in+-- order. @h@ is the per-field @Category@ (e.g. @(->)@, @(:~:)@, @Kleisli m@,+-- or a @Basic m@ from an @Override@). Returns @h@ (which must not mention the+-- parameters). Constants and one-parameter shapes have no @id@\/@(.)@, so they+-- yield 'Nothing' and the whole synthesis bails.+classifyCatField :: TyVar -> TyVar -> Type -> Maybe Type+classifyCatField atv btv ft+ | Just (hp, qarg) <- splitAppTy_maybe ft+ , qarg `eqType` mkTyVarTy btv+ , Just (h, parg) <- splitAppTy_maybe hp+ , parg `eqType` mkTyVarTy atv+ , not (atv `elemVarSet` tyCoVarsOfType h)+ , not (btv `elemVarSet` tyCoVarsOfType h) = Just h+ | otherwise = Nothing++-- | How one field of a @Category@ product is handled: it is a @Category@ @h@+-- (with a @realFt(t1,t2) ~R h t1 t2@ coercion builder — 'Refl' unless reshaped+-- by an @Override2@), or a /constant/ @m@ handled Const-style via its @Monoid@+-- (@id = mempty@, @(.) = (\<>)@) — the automatic, @Basic@-free version.+data CatFld = CatF Type (Type -> Type -> Coercion) | MonF Type++-- | Synthesize @Category (Stock2 P)@ for a single-constructor product whose+-- every field is a @Category@ in the two parameters (shape @h a b@). @id@ and+-- @(.)@ are pointwise — @id = P id .. id@, @P g.. . P h.. = P (g.h)..@ — exactly+-- the @Semigroup@ pattern lifted to two parameters. @Category@ is poly-kinded+-- (@cat :: k -> k -> Type@), so the kind @k@ (here always @Type@) is threaded+-- through the dictionary and through every @id@\/@(.)@ at the field categories.+--+-- @P@ may be wrapped in @Override2 cfg P@: then each positional modifier @m@+-- reshapes its field to @m a b@ (the modifier applied to both parameters), with+-- a per-field @realFt ~R m a b@ coercion, so fields that are not yet categories+-- (an @Int@, an @a -> Maybe b@) become ones (@Basic (Sum Int)@, @Kleisli Maybe@).+synthCategory :: GenEnv -> Class -> CtLoc -> Type -> Type -> TcPluginM (Maybe (EvTerm, [Ct]))+synthCategory gen catCls loc wrappedTy p0 =+ case geStock2 gen of+ Just st2Tc+ -- peel an optional @Override2 cfg P@: @realP@ is the genuine constructor,+ -- @mMods@ the per-field positional modifiers (single inner list — one ctor).+ | let (realP, mMods) = case geOverride2 gen of+ Just ov2Tc+ | Just (tc, [_, rp, cfg]) <- splitTyConApp_maybe p0, tc == ov2Tc+ -> (rp, listToMaybe =<< decodePositional cfg)+ _ -> (p0, Nothing)+ , Just pTc <- tyConAppTyCon_maybe realP+ , [dc] <- tyConDataCons pTc, not (isNewTyCon pTc) -> do+ monoidCls <- tcLookupClass monoidClassName+ let fixed = tyConAppArgs realP+ idSel = classMethod "id" catCls+ compSel = classMethod "." catCls+ memptySel = classMethod "mempty" monoidCls+ mappendSel = classMethod "mappend" monoidCls+ wargs = tyConAppArgs wrappedTy -- [k, k, P] (P may be Override2 …)+ kTy = head wargs -- the kind k (Type here)+ dictCon = dataConWorkId (classDataCon catCls)+ app2 m t1 t2 = mkAppTy (mkAppTy m t1) t2+ instAt t1 t2 = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [t1, t2]))+ isKeep m = maybe False (\k -> tyConAppTyCon_maybe m == Just k) (geKeep gen)+ -- @Stock2 P a b ~R P a b@, then (if present) @Override2 cfg rp a b ~R rp a b@+ coDown t1 t2 = mkTransCo+ (mkUnbranchedAxInstCo Representational (newTyConCo st2Tc) (wargs ++ [t1, t2]) [])+ (case geOverride2 gen of+ Just ov2Tc | tyConAppTyCon_maybe p0 == Just ov2Tc ->+ mkUnbranchedAxInstCo Representational (newTyConCo ov2Tc)+ (tyConAppArgs p0 ++ [t1, t2]) []+ _ -> mkRepReflCo (app2 realP t1 t2))+ cast' e co = if isReflCo co then e else Cast e co+ pTv <- freshTyVar "p" ; qTv <- freshTyVar "q"+ let realFtsPQ = instAt (mkTyVarTy pTv) (mkTyVarTy qTv)+ inPQ t = pTv `elemVarSet` tyCoVarsOfType t || qTv `elemVarSet` tyCoVarsOfType t+ -- per field: a Category @h@ (+ coercion), or a constant @m@ handled+ -- Const-style via its Monoid (the automatic, @Basic@-free path).+ resolve i ftPQ = case mMods of+ Just mods | Just m <- safeIdx mods i, not (isKeep m) ->+ Just (CatF m (\t1 t2 -> mkStockCo (PluginProv "stock") Representational+ (instAt t1 t2 !! i) (app2 m t1 t2)))+ _ -> case classifyCatField pTv qTv ftPQ of+ Just h -> Just (CatF h (\t1 t2 -> mkRepReflCo (instAt t1 t2 !! i)))+ Nothing | not (inPQ ftPQ) -> Just (MonF ftPQ) -- constant ⇒ Monoid+ | otherwise -> Nothing -- mentions a/b but not @h a b@+ badLen = maybe False ((/= length realFtsPQ) . length) mMods+ case if badLen then Nothing+ else traverse (uncurry resolve) (zip [0 :: Int ..] realFtsPQ) of+ Nothing -> pure Nothing+ Just flds -> do+ -- per-field dictionary: @Category h@, or @Monoid m@ for a constant+ dws <- traverse (\fld -> case fld of+ CatF h _ -> do ev <- newWanted loc (mkClassPred catCls [kTy, h])+ pure (ctEvExpr ev, mkNonCanonical ev)+ MonF m -> do ev <- newWanted loc (mkClassPred monoidCls [m])+ pure (ctEvExpr ev, mkNonCanonical ev)) flds+ let (dEs, dWs) = unzip dws+ -- id = /\a. (P <id of each field>..) |> sym (Stock2(..) a a ~ P a a)+ aTv <- freshTyVar "a"+ let aTy = mkTyVarTy aTv+ idVal (CatF h coFn) dE = cast' (mkApps (Var idSel) [Type kTy, Type h, dE, Type aTy])+ (mkSymCo (coFn aTy aTy))+ idVal (MonF m) dE = mkApps (Var memptySel) [Type m, dE] -- id = mempty+ idImpl = Lam aTv (Cast (mkCoreConApps dc (map Type (fixed ++ [aTy, aTy])+ ++ zipWith idVal flds dEs))+ (mkSymCo (coDown aTy aTy)))+ -- (.) = /\b c a. \g h. case g|>co of P g.. -> case h|>co of P h.. -> (P (g.h)..)|>sym+ bTv <- freshTyVar "b" ; cTv <- freshTyVar "c" ; a2Tv <- freshTyVar "a"+ let bTy = mkTyVarTy bTv ; cTy = mkTyVarTy cTv ; a2Ty = mkTyVarTy a2Tv+ resTy = mkAppTy (mkAppTy wrappedTy a2Ty) cTy -- Stock2(..) a c+ gId <- freshId (mkAppTy (mkAppTy wrappedTy bTy) cTy) "g"+ hId <- freshId (mkAppTy (mkAppTy wrappedTy a2Ty) bTy) "h"+ gIds <- zipWithM (\n t -> freshId t ("g" ++ show n)) [0 :: Int ..] (instAt bTy cTy)+ hIds <- zipWithM (\n t -> freshId t ("h" ++ show n)) [0 :: Int ..] (instAt a2Ty bTy)+ gCb <- freshId (mkTyConApp pTc (fixed ++ [bTy, cTy])) "gcb"+ hCb <- freshId (mkTyConApp pTc (fixed ++ [a2Ty, bTy])) "hcb"+ let compVal (CatF h coFn) dE gi hi =+ cast' (mkApps (Var compSel)+ [ Type kTy, Type h, dE, Type bTy, Type cTy, Type a2Ty+ , cast' (Var gi) (coFn bTy cTy), cast' (Var hi) (coFn a2Ty bTy) ])+ (mkSymCo (coFn a2Ty cTy))+ compVal (MonF m) dE gi hi =+ mkApps (Var mappendSel) [Type m, dE, Var gi, Var hi] -- g . h = g <> h+ comps = zipWith4 compVal flds dEs gIds hIds+ resCast = Cast (mkCoreConApps dc (map Type (fixed ++ [a2Ty, cTy]) ++ comps))+ (mkSymCo (coDown a2Ty cTy))+ inner = Case (Cast (Var hId) (coDown a2Ty bTy)) hCb resTy [Alt (DataAlt dc) hIds resCast]+ body = Case (Cast (Var gId) (coDown bTy cTy)) gCb resTy [Alt (DataAlt dc) gIds inner]+ compImpl = mkLams [bTv, cTv, a2Tv, gId, hId] body+ dict = mkApps (Var dictCon) [Type kTy, Type wrappedTy, idImpl, compImpl]+ pure (Just (EvExpr dict, dWs))+ _ -> pure Nothing++-- | Total list indexing.+safeIdx :: [a] -> Int -> Maybe a+safeIdx xs i = if i >= 0 && i < length xs then Just (xs !! i) else Nothing+++-- | Synthesize @Bifoldable (Stock2 P)@. @bifoldMap@ maps @a@-fields with the+-- first function, @b@-fields with the second, folds @h a@/@h b@ fields with+-- @h@'s own @foldMap@, drops constants, and combines with @(<>)@; all other+-- methods come from the class defaults. No superclass (unlike @Bifunctor@).+synthBifoldable :: GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthBifoldable gen cls loc wrappedTy p =+ case (geStock2 gen, tyConAppTyCon_maybe realP) of+ (Just st2Tc, Just pTc) -> do+ monoidCls <- tcLookupClass monoidClassName+ foldableCls <- tcLookupClass foldableClassName+ let fixed = tyConAppArgs realP+ dcons = tyConDataCons pTc+ foldMapSel = classMethod "foldMap" foldableCls+ memptySel = classMethod "mempty" monoidCls+ mappendSel = classMethod "mappend" monoidCls+ coAt t1 t2 = coDown2With (geOverride2 gen) st2Tc wrappedTy p realP t1 t2+ mtv <- freshTyVar "m" ; atv <- freshTyVar "a" ; btv <- freshTyVar "b"+ let mTy = mkTyVarTy mtv ; aTy = mkTyVarTy atv ; bTy = mkTyVarTy btv+ innerAB = mkTyConApp pTc (fixed ++ [aTy, bTy])+ dM <- freshId (mkClassPred monoidCls [mTy]) "dM"+ gA <- freshId (mkVisFunTyMany aTy mTy) "gA"+ gB <- freshId (mkVisFunTyMany bTy mTy) "gB"+ tId <- freshId (mkAppTy (mkAppTy wrappedTy aTy) bTy) "t"+ cb <- freshId innerAB "cb"+ let memptyE = mkApps (Var memptySel) [Type mTy, Var dM]+ mappendE x y = mkApps (Var mappendSel) [Type mTy, Var dM, x, y]+ -- fold an @h pTy@ field via the modifier @m@'s @foldMap@, casting the+ -- field value @h pTy ~R m pTy@ first.+ foldOver i h g pTy x = do+ let m = fromMaybe h (override1Mod gen mMods i)+ ev <- newWanted loc (mkClassPred foldableCls [m])+ pure (Just (Just ( mkApps (Var foldMapSel)+ [Type m, ctEvExpr ev, Type mTy, Type pTy, Var dM, Var g+ , castReshape (Var x) (reshapeCo h m pTy)]+ , [mkNonCanonical ev] )))+ contrib i x ft = case classifyBiField atv btv aTy bTy ft of+ Nothing -> pure Nothing+ Just BFConst -> pure (Just Nothing)+ Just BFA -> pure (Just (Just (App (Var gA) (Var x), [])))+ Just BFB -> pure (Just (Just (App (Var gB) (Var x), [])))+ Just (BFFoldA h) -> foldOver i h gA aTy x+ Just (BFFoldB h) -> foldOver i h gB bTy x+ malts <- forM dcons \dc -> do+ let fts = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [aTy, bTy]))+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] fts+ mcs <- sequence (zipWith3 contrib [0 :: Int ..] xs fts)+ case sequence mcs of+ Nothing -> pure Nothing+ Just contribs ->+ let (es, wss) = unzip (catMaybes contribs)+ body = if null es then memptyE else foldr1 mappendE es+ in pure (Just (Alt (DataAlt dc) xs body, concat wss))+ -- @bifoldr@ (so a lazy bi-fold does not fall back to the @Endo@ default,+ -- which drags the @Stock2@ coercion along). @bifoldr f g z (Con .. xi ..)@+ -- nests a contribution per field around @z@: a constant passes the+ -- accumulator through; an @a@\/@b@ field is @f xi rest@\/@g xi rest@; an+ -- @h a@\/@h b@ field is @(\\b1 b2 -> foldr f b2 b1) xi rest@ (GHC's flip+ -- shape). @bifoldr@'s forall order is @a c b@. Skipped under @Override2@.+ let foldrSel = classMethod "foldr" foldableCls+ bidxOf nm = head [ i | (i, m) <- zip [0 :: Int ..] (classMethods cls)+ , occNameString (occName m) == nm ]+ rcTv <- freshTyVar "c" ; raTv <- freshTyVar "a" ; rbTv <- freshTyVar "b"+ let rcTy = mkTyVarTy rcTv ; raTy = mkTyVarTy raTv ; rbTy = mkTyVarTy rbTv+ rfId <- freshId (mkVisFunTyMany raTy (mkVisFunTyMany rcTy rcTy)) "f"+ rgId <- freshId (mkVisFunTyMany rbTy (mkVisFunTyMany rcTy rcTy)) "g"+ rzId <- freshId rcTy "z"+ rtId <- freshId (mkAppTy (mkAppTy wrappedTy raTy) rbTy) "t"+ rcb <- freshId (mkTyConApp pTc (fixed ++ [raTy, rbTy])) "cb"+ let foldrField h fn elemTy x k = do+ ev <- newWanted loc (mkClassPred foldableCls [h])+ b1 <- freshId (mkAppTy h elemTy) "b1" ; b2 <- freshId rcTy "b2"+ let flipLam = mkLams [b1, b2] (mkApps (Var foldrSel)+ [Type h, ctEvExpr ev, Type elemTy, Type rcTy, Var fn, Var b2, Var b1])+ pure (Just (mkApps flipLam [Var x, k], [mkNonCanonical ev]))+ contribBR x ft k = case classifyBiField raTv rbTv raTy rbTy ft of+ Nothing -> pure Nothing+ Just BFConst -> pure (Just (k, []))+ Just BFA -> pure (Just (mkApps (Var rfId) [Var x, k], []))+ Just BFB -> pure (Just (mkApps (Var rgId) [Var x, k], []))+ Just (BFFoldA h) -> foldrField h rfId raTy x k+ Just (BFFoldB h) -> foldrField h rgId rbTy x k+ combineBR [] k = pure (Just (k, []))+ combineBR ((ft, x) : r) k = do+ mr <- combineBR r k+ case mr of+ Nothing -> pure Nothing+ Just (k', w') -> do mc <- contribBR x ft k'+ pure (fmap (\(e, w) -> (e, w ++ w')) mc)+ mBiFoldrAlts <- if isJust mMods then pure Nothing else fmap sequence $ forM dcons \dc -> do+ let fts = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [raTy, rbTy]))+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] fts+ mb <- combineBR (zip fts xs) (Var rzId)+ pure (fmap (\(body, w) -> (Alt (DataAlt dc) xs body, w)) mb)+ case sequence malts of+ Nothing -> pure Nothing+ Just altWss -> do+ let (alts, wss) = unzip altWss+ biFoldMapImpl = mkLams [mtv, atv, btv, dM, gA, gB, tId]+ (destructInner pTc (fixed ++ [aTy, bTy])+ (Cast (Var tId) (coAt aTy bTy)) cb mTy alts)+ (biFoldrMethods, biFoldrWs) = case mBiFoldrAlts of+ Just altWs ->+ let (rAlts, rWss) = unzip altWs+ biFoldrImpl = mkLams [raTv, rcTv, rbTv, rfId, rgId, rzId, rtId]+ (destructInner pTc (fixed ++ [raTy, rbTy])+ (Cast (Var rtId) (coAt raTy rbTy)) rcb rcTy rAlts)+ in ([(bidxOf "bifoldr", biFoldrImpl)], concat rWss)+ Nothing -> ([], [])+ dict <- recDictWith cls wrappedTy []+ ((bidxOf "bifoldMap", biFoldMapImpl) : biFoldrMethods)+ pure (Just (EvExpr dict, concat wss ++ biFoldrWs))+ _ -> pure Nothing+ where (realP, mMods) = peelOverride2With (ovTcsGen "Override2" gen) p++-- | Synthesize @Bitraversable (Stock2 P)@, directly (not by coercion — like+-- @Traversable@, @bitraverse@'s result @f (t c d)@ puts the wrapper under an+-- abstract applicative, so DerivingVia can't coerce it onto @P@; the instance+-- is usable at @Stock2 P@ / via the one-liner). Per constructor,+-- @pure mkCon \<*\> f1 \<*\> …@: an @a@\/@b@ field uses the supplied function,+-- a constant uses @pure@, and an @h a@\/@h b@ field uses @traverse \@h@ (an+-- @Override2@-reshaped functor goes through the modifier, re-wrapped with+-- @pure coerce \<*\> _@). @Bifunctor@ and @Bifoldable@ superclasses come from+-- their own synthesizers.+synthBitraversable :: GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthBitraversable gen bitravCls loc wrappedTy p =+ case (geStock2 gen, tyConAppTyCon_maybe realP) of+ (Just st2Tc, Just pTc) -> do+ appCls <- tcLookupClass applicativeClassName+ travCls <- tcLookupClass traversableClassName+ let fixed = tyConAppArgs realP+ dcons = tyConDataCons pTc+ traverseSel = classMethod "traverse" travCls+ pureSel = classMethod "pure" appCls+ apSel = classMethod "<*>" appCls+ coAt t1 t2 = coDown2With (geOverride2 gen) st2Tc wrappedTy p realP t1 t2+ fTv <- freshTyVarK (mkVisFunTyMany liftedTypeKind liftedTypeKind) "f" -- f :: Type -> Type+ aTv <- freshTyVar "a" ; cTv <- freshTyVar "c"+ bTv <- freshTyVar "b" ; dTv <- freshTyVar "d" -- bitraverse: forall f a c b d+ let fTy = mkTyVarTy fTv+ aTy = mkTyVarTy aTv ; cTy = mkTyVarTy cTv+ bTy = mkTyVarTy bTv ; dTy = mkTyVarTy dTv+ fOf t = mkAppTy fTy t+ innerAB = mkTyConApp pTc (fixed ++ [aTy, bTy])+ stcdTy = mkAppTy (mkAppTy wrappedTy cTy) dTy -- Stock2 P c d+ dApp <- freshId (mkClassPred appCls [fTy]) "dApp"+ gA <- freshId (mkVisFunTyMany aTy (fOf cTy)) "gA" -- a -> f c+ gB <- freshId (mkVisFunTyMany bTy (fOf dTy)) "gB" -- b -> f d+ tId <- freshId (mkAppTy (mkAppTy wrappedTy aTy) bTy) "t"+ cb <- freshId innerAB "cb"+ let pureE ty e = mkApps (Var pureSel) [Type fTy, Var dApp, Type ty, e]+ apE tyA tyB ac fe = mkApps (Var apSel) [Type fTy, Var dApp, Type tyA, Type tyB, ac, fe]+ -- traverse a sub-functor @h@ field at (inParam → outParam) with @g@;+ -- under Override2 reshape @h → m@, re-wrap @m out -> h out@.+ travField i h g inTy outTy x = case override1Mod gen mMods i of+ Nothing -> do+ ev <- newWanted loc (mkClassPred travCls [h])+ pure (Just ( mkApps (Var traverseSel)+ [Type h, ctEvExpr ev, Type fTy, Type inTy, Type outTy+ , Var dApp, Var g, Var x] -- :: f (h out)+ , [mkNonCanonical ev] ))+ Just m -> do+ ev <- newWanted loc (mkClassPred travCls [m])+ let trav = mkApps (Var traverseSel)+ [Type m, ctEvExpr ev, Type fTy, Type inTy, Type outTy+ , Var dApp, Var g, castReshape (Var x) (reshapeCo h m inTy)] -- f (m out)+ hOut = mkAppTy h outTy ; mOut = mkAppTy m outTy+ mo <- freshId mOut "mo"+ let coerceFn = Lam mo (castReshape (Var mo) (reshapeCo m h outTy)) -- m out -> h out+ pure (Just ( apE mOut hOut (pureE (mkVisFunTyMany mOut hOut) coerceFn) trav+ , [mkNonCanonical ev] ))+ fieldOf i x ftA = case classifyBiField aTv bTv aTy bTy ftA of+ Nothing -> pure Nothing+ Just BFConst -> pure (Just (pureE ftA (Var x), []))+ Just BFA -> pure (Just (App (Var gA) (Var x), []))+ Just BFB -> pure (Just (App (Var gB) (Var x), []))+ Just (BFFoldA h) -> travField i h gA aTy cTy x+ Just (BFFoldB h) -> travField i h gB bTy dTy x+ malts <- forM dcons \dc -> do+ let fts = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [aTy, bTy]))+ rvFts = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [cTy, dTy]))+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] fts+ mfes <- sequence (zipWith3 fieldOf [0 :: Int ..] xs fts)+ case sequence mfes of+ Nothing -> pure Nothing+ Just fes -> do+ let (fieldExprs, wss) = unzip fes+ ys <- zipWithM (\n ft -> freshId ft ("y" ++ show n)) [0 :: Int ..] rvFts+ let mkCon = mkLams ys (Cast (mkCoreConApps dc (map Type (fixed ++ [cTy, dTy]) ++ map Var ys))+ (mkSymCo (coAt cTy dTy)))+ rs = scanr mkVisFunTyMany stcdTy rvFts+ body = foldl (\ac (k, fe, rvFt) -> apE rvFt (rs !! (k + 1)) ac fe)+ (pureE (head rs) mkCon)+ (zip3 [0 :: Int ..] fieldExprs rvFts)+ pure (Just (Alt (DataAlt dc) xs body, concat wss))+ case sequence malts of+ Nothing -> pure Nothing+ Just altWss -> do+ let (alts, wss) = unzip altWss+ bitraverseImpl = mkLams [fTv, aTv, cTv, bTv, dTv, dApp, gA, gB, tId]+ (destructInner pTc (fixed ++ [aTy, bTy]) (Cast (Var tId) (coAt aTy bTy)) cb (fOf stcdTy) alts)+ -- superclasses (Bifunctor, Bifoldable) in classSCTheta order+ superClss = [ c | pr <- classSCTheta bitravCls, ClassPred c _ <- [classifyPredType pr] ]+ superDictsM <- forM superClss \c ->+ case occNameString (nameOccName (className c)) of+ "Bifunctor" -> synthBifunctor gen c loc wrappedTy p+ "Bifoldable" -> synthBifoldable gen c loc wrappedTy p+ _ -> pure Nothing+ case sequence superDictsM of+ Nothing -> pure Nothing+ Just sds -> do+ dict <- recDictWith bitravCls wrappedTy (map (unwrapEv . fst) sds) [(0, bitraverseImpl)]+ pure (Just (EvExpr dict, concatMap snd sds ++ concat wss))+ _ -> pure Nothing+ where (realP, mMods) = peelOverride2With (ovTcsGen "Override2" gen) p++-- | Synthesize @Bifunctor (Stock2 P)@. @bimap@ maps @a@-fields with the first+-- function and @b@-fields with the second; @first@/@second@ come from the class+-- defaults. @Bifunctor@ has a quantified superclass @forall a. Functor (p a)@,+-- which we supply by synthesizing the @Functor (Stock2 P a)@ dictionary under a+-- type-lambda (the @Functor@ maps the second parameter).+synthBifunctor :: GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthBifunctor gen cls loc wrappedTy p =+ case (geStock2 gen, tyConAppTyCon_maybe realP) of+ (Just st2Tc, Just pTc) -> do+ functorCls <- tcLookupClass functorClassName+ let fixed = tyConAppArgs realP+ dcons = tyConDataCons pTc+ bimapSel = classMethod "bimap" cls -- bimap+ coAt t1 t2 = coDown2With (geOverride2 gen) st2Tc wrappedTy p realP t1 t2+ apTv <- freshTyVar "a'" ; aTv <- freshTyVar "a"+ bpTv <- freshTyVar "b'" ; bTv <- freshTyVar "b"+ let apTy = mkTyVarTy apTv ; aTy = mkTyVarTy aTv+ bpTy = mkTyVarTy bpTv ; bTy = mkTyVarTy bTv+ innerAB = mkTyConApp pTc (fixed ++ [aTy, bTy])+ gA <- freshId (mkVisFunTyMany aTy apTy) "gA" -- a -> a'+ gB <- freshId (mkVisFunTyMany bTy bpTy) "gB" -- b -> b'+ sf <- freshId (mkAppTy (mkAppTy wrappedTy aTy) bTy) "sf"+ cb <- freshId innerAB "cb"+ -- map one field (instantiated at [a,b]) to its [a',b'] image — the+ -- n-ary variance engine at [Co, Co], so it also descends through arrows+ -- and nested functors (e.g. @[Either Int b]@) that the flat+ -- 'classifyBiField' cannot. Contravariant occurrences of a (covariant)+ -- parameter have no mapper, so they fail cleanly (no @mContra@).+ let bimapParams = [ (aTv, apTy, Just (Var gA), Nothing)+ , (bTv, bpTy, Just (Var gB), Nothing) ]+ -- a nested @q a b@ field: recurse via @q@'s own @bimap@ (so e.g.+ -- @Either a b@ / @(a, b)@ fields work, beyond the flat classifier).+ selfBi q = do+ ev <- newWanted loc (mkClassPred cls [q])+ pure (Just ( mkApps (Var bimapSel)+ [ Type q, ctEvExpr ev, Type aTy, Type apTy, Type bTy, Type bpTy+ , Var gA, Var gB ]+ , [mkNonCanonical ev] ))+ -- a plain field: map it pointwise with the n-ary engine.+ mapPlain x ft = do+ m <- varMapN functorCls Nothing loc bimapParams (Just selfBi) Cov ft+ pure (fmap (\(e, ws) -> (App e (Var x), ws)) m)+ -- under @Override2@, an @h a@/@h b@ field is reshaped to @mod a@/@mod b@:+ -- map via @mod@'s @fmap@ on the coerced value, then coerce the result back.+ mapField i x ft = case (override1Mod gen mMods i, classifyBiField aTv bTv aTy bTy ft) of+ (Just mod_, Just (BFFoldA h)) -> mapVia mod_ h x aTy apTy+ (Just mod_, Just (BFFoldB h)) -> mapVia mod_ h x bTy bpTy+ _ -> mapPlain x ft+ mapVia mod_ h x inTy outTy = do+ m <- varMapN functorCls Nothing loc bimapParams (Just selfBi) Cov (mkAppTy mod_ inTy)+ pure $ flip fmap m \(e, ws) ->+ ( Cast (App e (castReshape (Var x) (reshapeCo h mod_ inTy))) (mkSymCo (reshapeCo h mod_ outTy)), ws )+ malts <- forM dcons \dc -> do+ let fts = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [aTy, bTy]))+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] fts+ mfs <- sequence (zipWith3 mapField [0 :: Int ..] xs fts)+ case sequence mfs of+ Nothing -> pure Nothing+ Just pairs ->+ let (vals, wss) = unzip pairs+ body = Cast (mkCoreConApps dc (map Type (fixed ++ [apTy, bpTy]) ++ vals))+ (mkSymCo (coAt apTy bpTy))+ in pure (Just (Alt (DataAlt dc) xs body, concat wss))+ case sequence malts of+ Nothing -> pure Nothing+ Just altWss -> do+ let (alts, wss) = unzip altWss+ -- bimap quantifies @forall a b c d@: (a->b) maps the first param,+ -- (c->d) the second. So the binder order is input1,output1,input2,output2.+ bimapImpl = mkLams [aTv, apTv, bTv, bpTv, gA, gB, sf]+ (destructInner pTc (fixed ++ [aTy, bTy]) (Cast (Var sf) (coAt aTy bTy))+ cb (mkAppTy (mkAppTy wrappedTy apTy) bpTy) alts)+ dmFirst <- defMethId cls 1 -- first+ dmSecond <- defMethId cls 2 -- second+ fdmConst <- defMethId functorCls 1 -- Functor's (<$)+ -- The superclass forall a. Functor (Stock2 P a) is just @fmap = bimap+ -- id@ (a Bifunctor law): under @/\sc@ we build a @Functor (Stock2 P sc)@+ -- dictionary whose @fmap g = bimap id g@, reusing the Bifunctor dict.+ sctv <- freshTyVar "sc"+ b2tv <- freshTyVar "b" ; b2ptv <- freshTyVar "b'"+ zId <- freshId (mkTyVarTy sctv) "z"+ g2Id <- freshId (mkVisFunTyMany (mkTyVarTy b2tv) (mkTyVarTy b2ptv)) "g2"+ x2Id <- freshId (mkAppTy wrappedTy (mkTyVarTy sctv) `mkAppTy` mkTyVarTy b2tv) "x2"+ dict <- recClassDict cls wrappedTy \dvar -> do+ let scTy = mkTyVarTy sctv+ idA = Lam zId (Var zId) -- id @sc+ -- fmap g x = bimap @(Stock2 P) dvar @sc @sc @b @b' id g x+ fmapSC = mkLams [b2tv, b2ptv, g2Id, x2Id] $+ mkApps (Var bimapSel)+ [ Type wrappedTy, Var dvar+ , Type scTy, Type scTy, Type (mkTyVarTy b2tv), Type (mkTyVarTy b2ptv)+ , idA, Var g2Id, Var x2Id ]+ supDict <- recClassDict functorCls (mkAppTy wrappedTy scTy) \fdvar ->+ pure [ fmapSC+ , mkApps (Var fdmConst) [Type (mkAppTy wrappedTy scTy), Var fdvar] ]+ pure [ Lam sctv supDict+ , bimapImpl+ , mkApps (Var dmFirst) [Type wrappedTy, Var dvar]+ , mkApps (Var dmSecond) [Type wrappedTy, Var dvar] ]+ pure (Just (EvExpr dict, concat wss))+ _ -> pure Nothing+ where (realP, mMods) = peelOverride2With (ovTcsGen "Override2" gen) p++-- | A fresh kind-@Type@ type variable (for the @forall a b@ in @fmap@).++-- | The @Stock2@ counterpart of 'zipLift2': walk two values of the same+-- @Stock2 P@ shape (@fa :: P a c@, @fb :: P b d@) in lock-step, combining the+-- per-field-pair results of matching constructors. Shared by @liftEq2@+-- (short-circuit conjunction) and @liftCompare2@ (lexicographic).+zipLiftBi :: TyCon -> [Type] -> (Type -> Type -> Coercion)+ -> (Type, Type) -> (Type, Type) -> Type -- (a,c) for fa, (b,d) for fb, result+ -> Id -> Id -- the two scrutinees+ -> (Int -> Int -> CoreExpr) -- mismatched-constructor result+ -> ([CoreExpr] -> TcPluginM CoreExpr) -- combine field results+ -> (Int -> Type -> Id -> Id -> TcPluginM (Maybe (CoreExpr, [Ct]))) -- per field pair (with index)+ -> TcPluginM (Maybe (CoreExpr, [Ct]))+zipLiftBi pTc fixed coAt2 (aTy, cTy) (bTy, dTy) resTy faId fbId mismatch combine fieldOp = do+ let dcons = tyConDataCons pTc+ fieldsBi dc t1 t2 = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [t1, t2]))+ freshF dc t1 t2 = zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] (fieldsBi dc t1 t2)+ indexed = zip [0 :: Int ..] dcons+ mInner <- forM indexed \(i, dci) -> do+ xs <- freshF dci aTy cTy+ mAlts <- forM indexed \(j, dcj) -> do+ ys <- freshF dcj bTy dTy+ if i /= j+ then pure (Just (Alt (DataAlt dcj) ys (mismatch i j), []))+ else do+ mops <- sequence (zipWith4 fieldOp [0 :: Int ..] (fieldsBi dci aTy cTy) xs ys)+ case sequence mops of+ Nothing -> pure Nothing+ Just ows -> do body <- combine (map fst ows)+ pure (Just (Alt (DataAlt dcj) ys body, concatMap snd ows))+ case sequence mAlts of+ Nothing -> pure Nothing+ Just altWss -> do+ let (alts, wss) = unzip altWss+ cbB <- freshId (mkTyConApp pTc (fixed ++ [bTy, dTy])) "cbb"+ pure (Just ( Alt (DataAlt dci) xs+ (destructInner pTc (fixed ++ [bTy, dTy]) (Cast (Var fbId) (coAt2 bTy dTy)) cbB resTy alts)+ , concat wss ))+ case sequence mInner of+ Nothing -> pure Nothing+ Just altWss -> do+ let (alts, wss) = unzip altWss+ cbA <- freshId (mkTyConApp pTc (fixed ++ [aTy, cTy])) "cba"+ pure (Just ( destructInner pTc (fixed ++ [aTy, cTy]) (Cast (Var faId) (coAt2 aTy cTy)) cbA resTy alts+ , concat wss ))++-- | The superclass evidence for @C2 (Stock2 P)@: each entry of @C2@'s+-- @classSCTheta@ instantiated at the via-target and requested as a wanted+-- (discharged by the plugin: lifted built-ins, or the @Stock2@ passthrough).+stock2Supers :: Class -> Type -> CtLoc -> TcPluginM ([CoreExpr], [Ct])+stock2Supers cls wrappedTy loc = do+ let subst = case classTyVars cls of+ (tv : _) -> zipTvSubst [tv] [wrappedTy]+ _ -> emptySubst+ evs <- forM (map (substTy subst) (classSCTheta cls)) (newWanted loc)+ pure (map ctEvExpr evs, map mkNonCanonical evs)++-- | Synthesize @Eq2 (Stock2 P)@: @liftEq2@ is same-constructor-and-all-fields,+-- with @a@-fields compared by the first function, @b@-fields by the second,+-- @h a@\/@h b@ fields by @liftEq@, constants by @(==)@.+-- Override2 is transparent for Eq2: a hashing/forcing modifier does not change+-- structural equality, so peel the wrapper and compare the real fields. (This+-- makes @deriving Hashable2 via Overriding2 …@ work: its @Eq2@ superclass is+-- dragged through the same config.)+synthEq2 :: GenEnv -> Class -> CtLoc -> Type -> Type -> TcPluginM (Maybe (EvTerm, [Ct]))+synthEq2 gen eq2Cls loc wrappedTy p0 =+ case (geStock2 gen, tyConAppTyCon_maybe realP) of+ (Just st2Tc, Just pTc) -> do+ eqCls <- tcLookupClass eqClassName+ mEq1 <- lookupClassMaybe "Data.Functor.Classes" "Eq1"+ case mEq1 of+ Nothing -> pure Nothing+ Just eq1Cls -> do+ let fixed = tyConAppArgs realP+ liftEqSel = classMethod "liftEq" eq1Cls+ eqSel = classMethod "==" eqCls+ true_ = Var (dataConWorkId trueDataCon)+ false_ = Var (dataConWorkId falseDataCon)+ coAt2 t1 t2 = coDown2With (geOverride2 gen) st2Tc wrappedTy p0 realP t1 t2+ aTv <- freshTyVar "a" ; bTv <- freshTyVar "b" ; cTv <- freshTyVar "c" ; dTv <- freshTyVar "d"+ let aTy = mkTyVarTy aTv ; bTy = mkTyVarTy bTv ; cTy = mkTyVarTy cTv ; dTy = mkTyVarTy dTv+ eqAB <- freshId (mkVisFunTyMany aTy (mkVisFunTyMany bTy boolTy)) "eqAB"+ eqCD <- freshId (mkVisFunTyMany cTy (mkVisFunTyMany dTy boolTy)) "eqCD"+ faId <- freshId (mkAppTy (mkAppTy wrappedTy aTy) cTy) "fa"+ fbId <- freshId (mkAppTy (mkAppTy wrappedTy bTy) dTy) "fb"+ let conj [] = pure true_+ conj (e : more) = do rest <- conj more+ scr <- freshId boolTy "c"+ pure (Case e scr boolTy [ Alt (DataAlt falseDataCon) [] false_+ , Alt (DataAlt trueDataCon) [] rest ])+ fieldOp i ft x y = case classifyBiField aTv cTv aTy cTy ft of+ Nothing -> pure Nothing+ Just BFA -> pure (Just (mkApps (Var eqAB) [Var x, Var y], []))+ Just BFB -> pure (Just (mkApps (Var eqCD) [Var x, Var y], []))+ Just BFConst -> do ev <- newWanted loc (mkClassPred eqCls [ft])+ pure (Just (mkApps (Var eqSel) [Type ft, ctEvExpr ev, Var x, Var y], [mkNonCanonical ev]))+ Just (BFFoldA h) -> do let m = fromMaybe h (override1Mod gen mMods i)+ ev <- newWanted loc (mkClassPred eq1Cls [m])+ pure (Just (mkApps (Var liftEqSel) [Type m, ctEvExpr ev, Type aTy, Type bTy, Var eqAB, castReshape (Var x) (reshapeCo h m aTy), castReshape (Var y) (reshapeCo h m bTy)], [mkNonCanonical ev]))+ Just (BFFoldB h) -> do let m = fromMaybe h (override1Mod gen mMods i)+ ev <- newWanted loc (mkClassPred eq1Cls [m])+ pure (Just (mkApps (Var liftEqSel) [Type m, ctEvExpr ev, Type cTy, Type dTy, Var eqCD, castReshape (Var x) (reshapeCo h m cTy), castReshape (Var y) (reshapeCo h m dTy)], [mkNonCanonical ev]))+ mBody <- zipLiftBi pTc fixed coAt2 (aTy, cTy) (bTy, dTy) boolTy faId fbId (\_ _ -> false_) conj fieldOp+ case mBody of+ Nothing -> pure Nothing+ Just (body, ws) -> do+ (supers, scWs) <- stock2Supers eq2Cls wrappedTy loc+ let impl = mkLams [aTv, bTv, cTv, dTv, eqAB, eqCD, faId, fbId] body+ pure (Just (EvExpr (mkClassDict eq2Cls wrappedTy (supers ++ [impl])), scWs ++ ws))+ _ -> pure Nothing+ where (realP, mMods) = peelOverride2With (ovTcsGen "Override2" gen) p0++-- | Synthesize @Ord2 (Stock2 P)@: @liftCompare2@ orders by constructor tag,+-- then lexicographically by fields (first-param fields by the first function,+-- second by the second, @h a@\/@h b@ by @liftCompare@, constants by @compare@).+synthOrd2 :: GenEnv -> Class -> CtLoc -> Type -> Type -> TcPluginM (Maybe (EvTerm, [Ct]))+synthOrd2 gen ord2Cls loc wrappedTy p =+ case (geStock2 gen, tyConAppTyCon_maybe realP) of+ (Just st2Tc, Just pTc) -> do+ ordCls <- tcLookupClass ordClassName+ mOrd1 <- lookupClassMaybe "Data.Functor.Classes" "Ord1"+ case mOrd1 of+ Nothing -> pure Nothing+ Just ord1Cls -> do+ let fixed = tyConAppArgs realP+ liftCmpSel = classMethod "liftCompare" ord1Cls+ cmpSel = classMethod "compare" ordCls+ ordTy = mkTyConTy orderingTyCon+ [ltC, eqC, gtC] = tyConDataCons orderingTyCon+ ltE = Var (dataConWorkId ltC) ; eqE = Var (dataConWorkId eqC) ; gtE = Var (dataConWorkId gtC)+ coAt2 t1 t2 = coDown2With (geOverride2 gen) st2Tc wrappedTy p realP t1 t2+ aTv <- freshTyVar "a" ; bTv <- freshTyVar "b" ; cTv <- freshTyVar "c" ; dTv <- freshTyVar "d"+ let aTy = mkTyVarTy aTv ; bTy = mkTyVarTy bTv ; cTy = mkTyVarTy cTv ; dTy = mkTyVarTy dTv+ cmpAB <- freshId (mkVisFunTyMany aTy (mkVisFunTyMany bTy ordTy)) "cmpAB"+ cmpCD <- freshId (mkVisFunTyMany cTy (mkVisFunTyMany dTy ordTy)) "cmpCD"+ faId <- freshId (mkAppTy (mkAppTy wrappedTy aTy) cTy) "fa"+ fbId <- freshId (mkAppTy (mkAppTy wrappedTy bTy) dTy) "fb"+ let lexCmp [] = pure eqE+ lexCmp (e : more) = do rest <- lexCmp more+ scr <- freshId ordTy "o"+ pure (Case e scr ordTy [ Alt (DataAlt ltC) [] ltE+ , Alt (DataAlt eqC) [] rest+ , Alt (DataAlt gtC) [] gtE ])+ fieldOp i ft x y = case classifyBiField aTv cTv aTy cTy ft of+ Nothing -> pure Nothing+ Just BFA -> pure (Just (mkApps (Var cmpAB) [Var x, Var y], []))+ Just BFB -> pure (Just (mkApps (Var cmpCD) [Var x, Var y], []))+ Just BFConst -> do ev <- newWanted loc (mkClassPred ordCls [ft])+ pure (Just (mkApps (Var cmpSel) [Type ft, ctEvExpr ev, Var x, Var y], [mkNonCanonical ev]))+ Just (BFFoldA h) -> do let m = fromMaybe h (override1Mod gen mMods i)+ ev <- newWanted loc (mkClassPred ord1Cls [m])+ pure (Just (mkApps (Var liftCmpSel) [Type m, ctEvExpr ev, Type aTy, Type bTy, Var cmpAB, castReshape (Var x) (reshapeCo h m aTy), castReshape (Var y) (reshapeCo h m bTy)], [mkNonCanonical ev]))+ Just (BFFoldB h) -> do let m = fromMaybe h (override1Mod gen mMods i)+ ev <- newWanted loc (mkClassPred ord1Cls [m])+ pure (Just (mkApps (Var liftCmpSel) [Type m, ctEvExpr ev, Type cTy, Type dTy, Var cmpCD, castReshape (Var x) (reshapeCo h m cTy), castReshape (Var y) (reshapeCo h m dTy)], [mkNonCanonical ev]))+ mBody <- zipLiftBi pTc fixed coAt2 (aTy, cTy) (bTy, dTy) ordTy faId fbId+ (\i j -> if i < j then ltE else gtE) lexCmp fieldOp+ case mBody of+ Nothing -> pure Nothing+ Just (body, ws) -> do+ (supers, scWs) <- stock2Supers ord2Cls wrappedTy loc+ let impl = mkLams [aTv, bTv, cTv, dTv, cmpAB, cmpCD, faId, fbId] body+ pure (Just (EvExpr (mkClassDict ord2Cls wrappedTy (supers ++ [impl])), scWs ++ ws))+ _ -> pure Nothing+ where (realP, mMods) = peelOverride2With (ovTcsGen "Override2" gen) p++-- | Synthesize @Show2 (Stock2 P)@: @liftShowsPrec2@ renders like derived @Show@+-- (prefix / infix / record, precedence-parenthesised) but shows a first-param+-- field with @spA@, a second with @spB@, an @h a@\/@h b@ field with+-- @liftShowsPrec@, a constant with its own @showsPrec@.+synthShow2 :: GenEnv -> Class -> CtLoc -> Type -> Type -> TcPluginM (Maybe (EvTerm, [Ct]))+synthShow2 gen show2Cls loc wrappedTy p =+ case (geStock2 gen, tyConAppTyCon_maybe realP) of+ (Just st2Tc, Just pTc) -> do+ mShow1 <- lookupClassMaybe "Data.Functor.Classes" "Show1"+ case mShow1 of+ Nothing -> pure Nothing+ Just show1Cls -> do+ showCls <- lookupOrig gHC_INTERNAL_SHOW (mkTcOcc "Show") >>= tcLookupClass+ ordCls <- tcLookupClass ordClassName+ appendId <- tcLookupId appendName+ let fixed = tyConAppArgs realP+ dcons = tyConDataCons pTc+ showSTy = mkVisFunTyMany stringTy stringTy+ liftSpSel = classMethod "liftShowsPrec" show1Cls+ showsPrecSel = classMethod "showsPrec" showCls+ gtSel = classMethod ">" ordCls+ coAt2 t1 t2 = coDown2With (geOverride2 gen) st2Tc wrappedTy p realP t1 t2+ cons c t = mkCoreConApps consDataCon [Type charTy, c, t]+ append s t = mkApps (Var appendId) [Type charTy, s, t]+ str s = unsafeTcPluginTcM (mkStringExprFS (fsLit s))+ ordIntEv <- newWanted loc (mkClassPred ordCls [intTy])+ let ordIntDict = ctEvExpr ordIntEv+ aTv <- freshTyVar "a" ; bTv <- freshTyVar "b"+ let aTy = mkTyVarTy aTv ; bTy = mkTyVarTy bTv+ spTyOf t = mkVisFunTyMany intTy (mkVisFunTyMany t showSTy)+ slTyOf t = mkVisFunTyMany (mkListTy t) showSTy+ spA <- freshId (spTyOf aTy) "spA" ; slA <- freshId (slTyOf aTy) "slA"+ spB <- freshId (spTyOf bTy) "spB" ; slB <- freshId (slTyOf bTy) "slB"+ dId <- freshId intTy "d" ; vId <- freshId (mkAppTy (mkAppTy wrappedTy aTy) bTy) "v"+ let mkRenderer i ft xi = case classifyBiField aTv bTv aTy bTy ft of+ Nothing -> pure Nothing+ Just BFA -> pure (Just (\pr -> mkApps (Var spA) [mkUncheckedIntExpr pr, Var xi], []))+ Just BFB -> pure (Just (\pr -> mkApps (Var spB) [mkUncheckedIntExpr pr, Var xi], []))+ Just BFConst -> do ev <- newWanted loc (mkClassPred showCls [ft])+ pure (Just (\pr -> mkApps (Var showsPrecSel) [Type ft, ctEvExpr ev, mkUncheckedIntExpr pr, Var xi], [mkNonCanonical ev]))+ Just (BFFoldA h) -> do let m = fromMaybe h (override1Mod gen mMods i)+ ev <- newWanted loc (mkClassPred show1Cls [m])+ pure (Just (\pr -> mkApps (Var liftSpSel) [Type m, ctEvExpr ev, Type aTy, Var spA, Var slA, mkUncheckedIntExpr pr, castReshape (Var xi) (reshapeCo h m aTy)], [mkNonCanonical ev]))+ Just (BFFoldB h) -> do let m = fromMaybe h (override1Mod gen mMods i)+ ev <- newWanted loc (mkClassPred show1Cls [m])+ pure (Just (\pr -> mkApps (Var liftSpSel) [Type m, ctEvExpr ev, Type bTy, Var spB, Var slB, mkUncheckedIntExpr pr, castReshape (Var xi) (reshapeCo h m bTy)], [mkNonCanonical ev]))+ mAltWss <- forM dcons \dc -> do+ let fts = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [aTy, bTy]))+ name = occNameString (getOccName dc)+ labels = map (occNameString . nameOccName . flSelector) (dataConFieldLabels dc)+ nameStr <- str name+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] fts+ rest <- freshId stringTy "r"+ gtBndr <- freshId boolTy "pb"+ prec <- conPrec dc+ mRends <- sequence (zipWith3 mkRenderer [0 :: Int ..] fts xs)+ case sequence mRends of+ Nothing -> pure Nothing+ Just rends -> do+ let (renderers, wss) = unzip rends+ parenAt thr mk t =+ Case (mkApps (Var gtSel) [Type intTy, ordIntDict, Var dId, mkUncheckedIntExpr thr])+ gtBndr stringTy+ [ Alt (DataAlt falseDataCon) [] (mk t)+ , Alt (DataAlt trueDataCon) [] (cons (mkCharExpr '(') (mk (cons (mkCharExpr ')') t))) ]+ goPrefix t = foldr (\r acc -> cons (mkCharExpr ' ') (App (r 11) acc)) t renderers+ prefixBody t = append nameStr (goPrefix t)+ body <-+ if dataConIsInfix dc+ then do opStr <- str (" " ++ name ++ " ")+ let [l, r] = renderers+ mk t = App (l (prec + 1)) (append opStr (App (r (prec + 1)) t))+ pure (parenAt prec mk (Var rest))+ else if not (null labels)+ then do openB <- str " {" ; eqB <- str " = " ; commaB <- str ", " ; closeB <- str "}"+ lblStrs <- mapM str labels+ let recF = zip lblStrs renderers+ goRec [(lbl, r)] c = append lbl (append eqB (App (r 0) (append closeB c)))+ goRec ((lbl, r) : m) c = append lbl (append eqB (App (r 0) (append commaB (goRec m c))))+ goRec [] c = append closeB c+ recBody t = append nameStr (append openB (goRec recF t))+ pure (parenAt 10 recBody (Var rest))+ else if null xs then pure (append nameStr (Var rest))+ else pure (parenAt 10 prefixBody (Var rest))+ pure (Just (Alt (DataAlt dc) xs (Lam rest body), concat wss))+ case sequence mAltWss of+ Nothing -> pure Nothing+ Just altWss -> do+ let (alts, wss) = unzip altWss+ cb <- freshId (mkTyConApp pTc (fixed ++ [aTy, bTy])) "cb"+ let impl = mkLams [aTv, bTv, spA, slA, spB, slB, dId, vId]+ (destructInner pTc (fixed ++ [aTy, bTy]) (Cast (Var vId) (coAt2 aTy bTy)) cb showSTy alts)+ (supers, scWs) <- stock2Supers show2Cls wrappedTy loc+ dict <- recDictWith show2Cls wrappedTy supers [(0, impl)]+ pure (Just (EvExpr dict, mkNonCanonical ordIntEv : scWs ++ concat wss))+ _ -> pure Nothing+ where (realP, mMods) = peelOverride2With (ovTcsGen "Override2" gen) p++-- | Synthesize @Read2 (Stock2 P)@: @liftReadsPrec2@ parses like derived @Read@+-- (prefix / infix / record, precedence-aware) but reads a first-param field+-- with @rp1@, a second with @rp2@, an @h a@\/@h b@ field with @liftReadsPrec@,+-- a constant with its own @readsPrec@. The bivariate counterpart of+-- 'Stock.Classes1.synthRead1'; quantified superclasses come via 'stock2Supers'.+synthRead2 :: GenEnv -> Class -> CtLoc -> Type -> Type -> TcPluginM (Maybe (EvTerm, [Ct]))+synthRead2 gen read2Cls loc wrappedTy p =+ case (geStock2 gen, tyConAppTyCon_maybe realP) of+ (Just st2Tc, Just pTc) -> do+ mRead1 <- lookupClassMaybe "Data.Functor.Classes" "Read1"+ case mRead1 of+ Nothing -> pure Nothing+ Just read1Cls -> do+ readCls <- lookupOrig gHC_INTERNAL_READ (mkTcOcc "Read") >>= tcLookupClass+ ordCls <- tcLookupClass ordClassName+ appendId <- tcLookupId appendName+ eqStringId <- tcLookupId eqStringName+ lexId <- lookupOrig gHC_INTERNAL_READ (mkVarOcc "lex") >>= tcLookupId+ readParenId <- lookupOrig gHC_INTERNAL_READ (mkVarOcc "readParen") >>= tcLookupId+ concatMapId <- lookupOrig gHC_INTERNAL_LIST (mkVarOcc "concatMap") >>= tcLookupId+ let liftRpSel = classMethod "liftReadsPrec" read1Cls+ readsPrecSel = classMethod "readsPrec" readCls+ gtSel = classMethod ">" ordCls+ fixed = tyConAppArgs realP+ dcons = tyConDataCons pTc+ coAt2 t1 t2 = coDown2With (geOverride2 gen) st2Tc wrappedTy p realP t1 t2+ ordIntEv <- newWanted loc (mkClassPred ordCls [intTy])+ let ordIntDict = ctEvExpr ordIntEv+ aTv <- freshTyVar "a" ; bTv <- freshTyVar "b"+ let aTy = mkTyVarTy aTv ; bTy = mkTyVarTy bTv+ innerAB = mkTyConApp pTc (fixed ++ [aTy, bTy])+ gabTy = mkAppTy (mkAppTy wrappedTy aTy) bTy+ readSOf t = mkVisFunTyMany stringTy (mkListTy (mkBoxedTupleTy [t, stringTy]))+ rpTyOf t = mkVisFunTyMany intTy (readSOf t) -- Int -> ReadS t+ rlTyOf t = readSOf (mkListTy t) -- ReadS [t]+ pairTy = mkBoxedTupleTy [gabTy, stringTy]+ strPairTy = mkBoxedTupleTy [stringTy, stringTy]+ listPair = mkListTy pairTy+ tup2 = tupleDataCon Boxed 2+ nilPair = mkNilExpr pairTy+ false_ = Var (dataConWorkId falseDataCon)+ toWrapped e = Cast e (mkSymCo (coAt2 aTy bTy))+ mkPairW v r = mkCoreConApps tup2 [Type gabTy, Type stringTy, v, r]+ concatMapTo srcElem fn src = mkApps (Var concatMapId) [Type srcElem, Type pairTy, fn, src]+ str s = unsafeTcPluginTcM (mkStringExprFS (fsLit s))+ rp1Id <- freshId (rpTyOf aTy) "rp1" ; rl1Id <- freshId (rlTyOf aTy) "rl1"+ rp2Id <- freshId (rpTyOf bTy) "rp2" ; rl2Id <- freshId (rlTyOf bTy) "rl2"+ dId <- freshId intTy "d" ; rId <- freshId stringTy "r"++ -- one field's reader @prec -> restString -> [(ft, String)]@.+ let resOf t = mkListTy (mkBoxedTupleTy [t, stringTy]) -- [(t, String)]+ -- read an @h a@/@h b@ field via the modifier @m@, then cast the+ -- parsed @[(m a,String)]@ back to the real @[(h a,String)]@.+ readFold tArg rpI rlI i h = do+ let mMod = override1Mod gen mMods i+ m = fromMaybe h mMod+ ev <- newWanted loc (mkClassPred read1Cls [m])+ let rdr prec rest =+ let parsed = mkApps (Var liftRpSel)+ [Type m, ctEvExpr ev, Type tArg, Var rpI, Var rlI+ , mkUncheckedIntExpr prec, rest]+ in case mMod of+ Nothing -> parsed+ Just _ -> Cast parsed (mkStockCo (PluginProv "stock") Representational+ (resOf (mkAppTy m tArg)) (resOf (mkAppTy h tArg)))+ pure (Just (rdr, [mkNonCanonical ev]))+ mkFieldReader i ft = case classifyBiField aTv bTv aTy bTy ft of+ Nothing -> pure Nothing+ Just BFA -> pure (Just ((\prec rest -> mkApps (Var rp1Id) [mkUncheckedIntExpr prec, rest]), []))+ Just BFB -> pure (Just ((\prec rest -> mkApps (Var rp2Id) [mkUncheckedIntExpr prec, rest]), []))+ Just BFConst -> do ev <- newWanted loc (mkClassPred readCls [ft])+ pure (Just ((\prec rest -> mkApps (Var readsPrecSel)+ [Type ft, ctEvExpr ev, mkUncheckedIntExpr prec, rest]), [mkNonCanonical ev]))+ Just (BFFoldA h) -> readFold aTy rp1Id rl1Id i h+ Just (BFFoldB h) -> readFold bTy rp2Id rl2Id i h++ let buildChain dc [] accRev restE =+ pure $ mkCoreConApps consDataCon+ [ Type pairTy+ , mkPairW (toWrapped (conAppAt innerAB dc (map Var (reverse accRev)))) restE+ , nilPair ]+ buildChain dc ((ft, rdr) : more) accRev restE = do+ a <- freshId ft "a" ; r' <- freshId stringTy "r"+ pc <- freshId (mkBoxedTupleTy [ft, stringTy]) "p"+ cb <- freshId (mkBoxedTupleTy [ft, stringTy]) "pc"+ rest <- buildChain dc more (a : accRev) (Var r')+ let parsed = rdr (11 :: Integer) restE+ lam = Lam pc (Case (Var pc) cb listPair [Alt (DataAlt tup2) [a, r'] rest])+ pure (concatMapTo (mkBoxedTupleTy [ft, stringTy]) lam parsed)++ expectTok expStr restE k = do+ pp <- freshId strPairTy "p"; cb <- freshId strPairTy "pc"+ tk <- freshId stringTy "t"; r' <- freshId stringTy "r"; ecb <- freshId boolTy "b"+ body <- k (Var r')+ let lam = Lam pp (Case (Var pp) cb listPair+ [Alt (DataAlt tup2) [tk, r']+ (Case (mkApps (Var eqStringId) [Var tk, expStr]) ecb listPair+ [ Alt (DataAlt falseDataCon) [] nilPair+ , Alt (DataAlt trueDataCon) [] body ])])+ pure (concatMapTo strPairTy lam (App (Var lexId) restE))++ parseFieldP prec ft rdr restE k = do+ pp <- freshId (mkBoxedTupleTy [ft, stringTy]) "p"+ cb <- freshId (mkBoxedTupleTy [ft, stringTy]) "pc"+ v <- freshId ft "v"; r' <- freshId stringTy "r"+ body <- k (Var v) (Var r')+ let lam = Lam pp (Case (Var pp) cb listPair [Alt (DataAlt tup2) [v, r'] body])+ pure (concatMapTo (mkBoxedTupleTy [ft, stringTy]) lam (rdr prec restE))++ recChain dc fields restAfterName = do+ openB <- str "{"; closeB <- str "}"; eqB <- str "="; commaB <- str ","+ let result accRev rEnd = mkCoreConApps consDataCon+ [ Type pairTy+ , mkPairW (toWrapped (conAppAt innerAB dc (reverse accRev))) rEnd+ , nilPair ]+ go restE accRev [] _ = expectTok closeB restE (\rEnd -> pure (result accRev rEnd))+ go restE accRev ((lbl, ft, rdr) : more) isFirst = do+ lblStr <- str lbl+ let after rr = expectTok lblStr rr \r1 ->+ expectTok eqB r1 \r2 ->+ parseFieldP (0 :: Integer) ft rdr r2 \v r3 ->+ go r3 (v : accRev) more False+ if isFirst then after restE else expectTok commaB restE after+ expectTok openB restAfterName (\r0 -> go r0 [] fields True)++ mParserWss <- forM dcons \dc -> do+ let fts = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [aTy, bTy]))+ name = occNameString (getOccName dc)+ labels = map (occNameString . nameOccName . flSelector) (dataConFieldLabels dc)+ nameStr <- str name+ mRdrs <- zipWithM mkFieldReader [0 :: Int ..] fts+ case sequence mRdrs of+ Nothing -> pure Nothing+ Just rdrPrs -> do+ let (rdrs, wss) = unzip rdrPrs+ gtThr thr = mkApps (Var gtSel) [Type intTy, ordIntDict, Var dId, mkUncheckedIntExpr thr]+ mkParser flag inner =+ App (mkApps (Var readParenId) [Type gabTy, flag, inner]) (Var rId)+ parserApp <-+ if dataConIsInfix dc+ then do+ prec <- conPrec dc+ let [(ft0, rdr0), (ft1, rdr1)] = zip fts rdrs+ r0 <- freshId stringTy "r0"+ body <- parseFieldP (prec + 1) ft0 rdr0 (Var r0) \x rA ->+ expectTok nameStr rA \rB ->+ parseFieldP (prec + 1) ft1 rdr1 rB \y rC ->+ pure $ mkCoreConApps consDataCon+ [ Type pairTy+ , mkPairW (toWrapped (conAppAt innerAB dc [x, y])) rC+ , nilPair ]+ pure (mkParser (gtThr prec) (Lam r0 body))+ else do+ r0 <- freshId stringTy "r0"+ ptok <- freshId strPairTy "pt"; tcb <- freshId strPairTy "ptc"+ tok <- freshId stringTy "tok"; r1 <- freshId stringTy "r1"; ecb <- freshId boolTy "bc"+ chain <- if null labels+ then buildChain dc (zip fts rdrs) [] (Var r1)+ else recChain dc (zip3 labels fts rdrs) (Var r1)+ let tokBody = Case (mkApps (Var eqStringId) [Var tok, nameStr]) ecb listPair+ [ Alt (DataAlt falseDataCon) [] nilPair+ , Alt (DataAlt trueDataCon) [] chain ]+ tokLam = Lam ptok (Case (Var ptok) tcb listPair+ [Alt (DataAlt tup2) [tok, r1] tokBody])+ inner = Lam r0 (concatMapTo strPairTy tokLam (App (Var lexId) (Var r0)))+ -- record syntax never needs surrounding parens (see Stock.Read)+ flag = if null fts || not (null labels) then false_ else gtThr (10 :: Integer)+ pure (mkParser flag inner)+ pure (Just (parserApp, concat wss))++ case sequence mParserWss of+ Nothing -> pure Nothing+ Just parserWss -> do+ let (parserApps, wss) = unzip parserWss+ liftRp2Impl = mkLams [aTv, bTv, rp1Id, rl1Id, rp2Id, rl2Id, dId, rId] $+ foldr (\e acc -> mkApps (Var appendId) [Type pairTy, e, acc]) nilPair parserApps+ (supers, scWs) <- stock2Supers read2Cls wrappedTy loc+ dict <- recDictWith read2Cls wrappedTy supers [(0, liftRp2Impl)]+ pure (Just (EvExpr dict, mkNonCanonical ordIntEv : scWs ++ concat wss))+ _ -> pure Nothing+ where (realP, mMods) = peelOverride2With (ovTcsGen "Override2" gen) p
+ plugin/Stock/Bounded.hs view
@@ -0,0 +1,35 @@+{-# LANGUAGE BlockArguments #-}+-- @head@/@last cons@ are guarded by the caller's+-- enum-or-single-constructor contract and the class always having its methods.+{-# OPTIONS_GHC -Wno-x-partial -Wno-unused-imports #-}++-- | @Bounded@ via the SOP-EDSL. @minBound@\/@maxBound@ are values, so this is+-- pure 'injectSOP' (no @matchSOP@): an enumeration injects its first\/last+-- (nullary) constructor; a single-constructor product injects that constructor+-- with each field set to its own @minBound@\/@maxBound@ ('pureFields' ++-- 'field'). A clean demonstration that the SDK alone expresses a real+-- synthesizer — this module needs nothing from the plugin substrate.+module Stock.Bounded (boundedDeriver) where++import GHC.Plugins+import GHC.Core.Class (classMethods)+import Stock.Derive++-- | The caller guarantees the type is an enumeration or a single constructor+-- (GHC's @Bounded@ deriving has the same restriction).+boundedDeriver :: Deriver+boundedDeriver = Deriver \cls dt -> do+ let cons = dtCons dt+ minSel = classMethod "minBound" cls+ maxSel = classMethod "maxBound" cls+ bound sel ft d = mkApps (Var sel) [Type ft, d]+ if all (null . conFields) cons+ then -- enumeration: first / last constructor+ pure (classDict cls (dtVia dt) [ injectSOP dt (head cons) []+ , injectSOP dt (last cons) [] ])+ else do -- single product: each field at its bound+ let con = productCon dt+ fds <- pureFields (\ft -> do d <- field cls ft; pure (ft, d)) con+ pure (classDict cls (dtVia dt)+ [ injectSOP dt con [ bound minSel ft d | (ft, d) <- fds ]+ , injectSOP dt con [ bound maxSel ft d | (ft, d) <- fds ] ])
+ plugin/Stock/Classes1.hs view
@@ -0,0 +1,436 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ViewPatterns #-}+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns -Wno-unused-imports #-}++-- | The lifted @Data.Functor.Classes@ hierarchy over @Stock1 F@: @Eq1@,+-- @Ord1@, @Show1@, @Read1@. Each is the structural synthesizer of its unlifted twin+-- (@Eq@\/@Ord@) with one change: a field that /is/ the functor parameter @a@+-- is handled by the supplied function argument (@liftEq@'s @eq@,+-- @liftCompare@'s @cmp@) instead of the field's own instance, and a field of+-- shape @H a@ recurses through @H@'s own lifted method.+--+-- Since base-4.18 these classes carry a /quantified/ superclass — @Eq1 f@+-- requires @forall a. Eq a => Eq (f a)@ and @Ord1 f@ likewise for @Ord@ — so+-- we synthesize those superclass dictionaries too (from the same lifted+-- method, instantiated at @eq = (==)@ \/ @cmp = compare@).+module Stock.Classes1 (synthEq1, synthOrd1, synthShow1, synthRead1) where++import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Core.Class (Class, className, classSCTheta, classSCSelId)+import GHC.Core.Predicate (mkClassPred, isClassPred)+import GHC.Builtin.Names (eqClassName, ordClassName, appendName, eqStringName)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import Stock.Compat (gHC_INTERNAL_SHOW, gHC_INTERNAL_READ, gHC_INTERNAL_LIST)+import Control.Monad (forM, zipWithM)+import Stock.Derive (classMethod, castInto)+import Stock.Internal -- 'castReshape' (skip-Refl cast) comes from here+import Data.Maybe (fromJust)++-- ----- the structural lifted methods --------------------------------------++-- | Build the @liftEq@ method body @\\\@a \@b eq fa fb -> …@ for @Stock1 F@,+-- or 'Nothing' if some field shape is unsupported. Returns the field-instance+-- wanteds (@Eq H@ for constant fields, @Eq1 H@ for @H a@ fields).+buildLiftEq :: GenEnv -> Class -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (CoreExpr, [Ct]))+buildLiftEq gen eq1Cls eqCls loc wrappedTy f =+ case (geStock1 gen, tyConAppTyCon_maybe realF) of+ (Just st1Tc, Just fTc) -> do+ let liftEqSel = classMethod "liftEq" eq1Cls+ eqSel = classMethod "==" eqCls+ fixed = tyConAppArgs realF+ true_ = Var (dataConWorkId trueDataCon)+ false_ = Var (dataConWorkId falseDataCon)+ coAt t = coDown1 gen st1Tc wrappedTy f realF t+ aTv <- freshTyVar "a" ; bTv <- freshTyVar "b"+ let aTy = mkTyVarTy aTv ; bTy = mkTyVarTy bTv+ eqFnTy = mkVisFunTyMany aTy (mkVisFunTyMany bTy boolTy)+ eqId <- freshId eqFnTy "eq"+ faId <- freshId (mkAppTy wrappedTy aTy) "fa"+ fbId <- freshId (mkAppTy wrappedTy bTy) "fb"++ -- one field-pair becomes a Bool: the parameter via @eq@, a constant via+ -- its own @(==)@, an @H a@ field via @liftEq \@m eq@ (the @Override1@+ -- modifier @m@; the field values cast @h ~R m@ via @coB@).+ let fieldEq i ft x y = interpField eqCls eq1Cls aTv aTy loc (override1Mod gen mMods i) Roles+ { onParam = mkApps (Var eqId) [Var x, Var y]+ , onConst = \ev t -> mkApps (Var eqSel) [Type t, ctEvExpr ev, Var x, Var y]+ , onApply = \ev m coB -> mkApps (Var liftEqSel)+ [ Type m, ctEvExpr ev, Type aTy, Type bTy, Var eqId+ , castReshape (Var x) (coB aTy), castReshape (Var y) (coB bTy) ]+ } ft+ -- conjunction with short-circuit: @case e of False -> False; True -> …@+ conj [] = pure true_+ conj (e : more) = do+ rest <- conj more+ scr <- freshId boolTy "c"+ pure (Case e scr boolTy [ Alt (DataAlt falseDataCon) [] false_+ , Alt (DataAlt trueDataCon) [] rest ])++ mBody <- zipLift2 fTc fixed coAt aTy bTy boolTy faId fbId+ (\_ _ -> false_) conj fieldEq+ pure (fmap (\(body, ws) -> (mkLams [aTv, bTv, eqId, faId, fbId] body, ws)) mBody)+ _ -> pure Nothing+ where (realF, mMods) = peelOverride1 gen f++-- | Build the @liftCompare@ method body for @Stock1 F@: tag order between+-- constructors, lexicographic within. Wanteds: @Ord H@ \/ @Ord1 H@ per field.+buildLiftCompare :: GenEnv -> Class -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (CoreExpr, [Ct]))+buildLiftCompare gen ord1Cls ordCls loc wrappedTy f =+ case (geStock1 gen, tyConAppTyCon_maybe realF) of+ (Just st1Tc, Just fTc) -> do+ let liftCmpSel = classMethod "liftCompare" ord1Cls+ cmpSel = classMethod "compare" ordCls+ fixed = tyConAppArgs realF+ ordTy = mkTyConTy orderingTyCon+ [ltC, eqC, gtC] = tyConDataCons orderingTyCon+ ltE = Var (dataConWorkId ltC)+ eqE = Var (dataConWorkId eqC)+ gtE = Var (dataConWorkId gtC)+ coAt t = coDown1 gen st1Tc wrappedTy f realF t+ aTv <- freshTyVar "a" ; bTv <- freshTyVar "b"+ let aTy = mkTyVarTy aTv ; bTy = mkTyVarTy bTv+ cmpFnTy = mkVisFunTyMany aTy (mkVisFunTyMany bTy ordTy)+ cmpId <- freshId cmpFnTy "cmp"+ faId <- freshId (mkAppTy wrappedTy aTy) "fa"+ fbId <- freshId (mkAppTy wrappedTy bTy) "fb"++ -- one field-pair becomes an Ordering: the parameter via @cmp@, a constant+ -- via its own @compare@, an @H a@ field via @liftCompare \@m cmp@.+ let fieldCmp i ft x y = interpField ordCls ord1Cls aTv aTy loc (override1Mod gen mMods i) Roles+ { onParam = mkApps (Var cmpId) [Var x, Var y]+ , onConst = \ev t -> mkApps (Var cmpSel) [Type t, ctEvExpr ev, Var x, Var y]+ , onApply = \ev m coB -> mkApps (Var liftCmpSel)+ [ Type m, ctEvExpr ev, Type aTy, Type bTy, Var cmpId+ , castReshape (Var x) (coB aTy), castReshape (Var y) (coB bTy) ]+ } ft+ -- lexicographic: @case e of LT -> LT; GT -> GT; EQ -> …@+ lexCmp [] = pure eqE+ lexCmp (e : more) = do+ rest <- lexCmp more+ scr <- freshId ordTy "o"+ pure (Case e scr ordTy [ Alt (DataAlt ltC) [] ltE+ , Alt (DataAlt eqC) [] rest+ , Alt (DataAlt gtC) [] gtE ])++ mBody <- zipLift2 fTc fixed coAt aTy bTy ordTy faId fbId+ (\i j -> if i < j then ltE else gtE) lexCmp fieldCmp+ pure (fmap (\(body, ws) -> (mkLams [aTv, bTv, cmpId, faId, fbId] body, ws)) mBody)+ _ -> pure Nothing+ where (realF, mMods) = peelOverride1 gen f++-- ----- quantified-superclass dictionaries ---------------------------------++-- | A quantified superclass @forall a. C a => D (g a)@ as evidence: bind @a@+-- and its @C a@ dictionary, then build the @D (g a)@ dictionary. The callback+-- receives @a@, @g a@, and the @C a@ dictionary binder. This is the shape+-- shared by every @Eq1@\/@Ord1@\/@Show1@\/@Read1@ superclass.+buildQuantSuper :: Class -> Type+ -> (Type -> Type -> Id -> TcPluginM CoreExpr)+ -> TcPluginM CoreExpr+buildQuantSuper baseCls gTy mk = do+ aTv <- freshTyVar "a"+ let aTy = mkTyVarTy aTv ; gaTy = mkAppTy gTy aTy+ dA <- freshId (mkClassPred baseCls [aTy]) "d"+ inner <- mk aTy gaTy dA+ pure (mkLams [aTv, dA] inner)++-- | @Eq T@ dictionary from an equality test @eqImpl :: T -> T -> Bool@.+mkEqDict :: Class -> Type -> CoreExpr -> TcPluginM CoreExpr+mkEqDict eqCls tT eqImpl = do+ x <- freshId tT "x" ; y <- freshId tT "y" ; s <- freshId boolTy "c"+ let neq = mkLams [x, y] (Case (mkApps eqImpl [Var x, Var y]) s boolTy+ [ Alt (DataAlt falseDataCon) [] (Var (dataConWorkId trueDataCon))+ , Alt (DataAlt trueDataCon) [] (Var (dataConWorkId falseDataCon)) ])+ pure (mkClassDict eqCls tT [eqImpl, neq])++-- | The quantified @Eq@ superclass @forall a. Eq a => Eq (g a)@, built from+-- the @liftEq@ method instantiated at @eq = (==) \@a@.+buildQuantEq :: Class -> Type -> CoreExpr -> TcPluginM CoreExpr+buildQuantEq eqCls gTy liftEqImpl =+ buildQuantSuper eqCls gTy \aTy gaTy dEqA -> do+ let eqA = mkApps (Var (classMethod "==" eqCls)) [Type aTy, Var dEqA]+ eqGA = mkApps liftEqImpl [Type aTy, Type aTy, eqA]+ mkEqDict eqCls gaTy eqGA++-- | The quantified @Ord@ superclass @forall a. Ord a => Ord (g a)@, built from+-- @liftCompare@ (instantiated at @compare \@a@) plus the @Eq (g a)@ it needs as+-- its own superclass (from @liftEq@ instantiated at the @Eq a@ inside @Ord a@).+buildQuantOrd :: Class -> Class -> Type -> CoreExpr -> CoreExpr -> TcPluginM CoreExpr+buildQuantOrd ordCls eqCls gTy liftCmpImpl liftEqImpl =+ buildQuantSuper ordCls gTy \aTy gaTy dOrdA -> do+ let cmpA = mkApps (Var (classMethod "compare" ordCls)) [Type aTy, Var dOrdA]+ cmpGA = mkApps liftCmpImpl [Type aTy, Type aTy, cmpA]+ dEqA = mkApps (Var (classSCSelId ordCls 0)) [Type aTy, Var dOrdA] -- Eq a from Ord a+ eqA = mkApps (Var (classMethod "==" eqCls)) [Type aTy, dEqA]+ eqGA = mkApps liftEqImpl [Type aTy, Type aTy, eqA]+ eqDictGa <- mkEqDict eqCls gaTy eqGA+ recDictWith ordCls gaTy [eqDictGa] [(0, cmpGA)]++-- ----- the two entry points -----------------------------------------------++synthEq1 :: GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthEq1 gen eq1Cls loc wrappedTy f = do+ eqCls <- tcLookupClass eqClassName+ m <- buildLiftEq gen eq1Cls eqCls loc wrappedTy f+ case m of+ Nothing -> pure Nothing+ Just (liftEqImpl, ws) -> do+ supers <- forM (classSCTheta eq1Cls) \_ -> buildQuantEq eqCls wrappedTy liftEqImpl+ pure (Just (EvExpr (mkClassDict eq1Cls wrappedTy (supers ++ [liftEqImpl])), ws))++-- ----- Show1 --------------------------------------------------------------++-- | Build @liftShowsPrec@'s body @\\\@a sp sl d v -> …@ for @Stock1 F@,+-- mirroring derived @showsPrec@ (prefix / infix / record / nullary, with the+-- @d > prec@ parenthesisation) but rendering the parameter field with the+-- supplied @sp@, an @H a@ field with @liftShowsPrec \@H sp sl@ (a @Show1 H@+-- wanted), and any other field with its own @showsPrec@ (a @Show H@ wanted).+buildLiftShowsPrec :: GenEnv -> Class -> Class -> Class -> Id -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (CoreExpr, [Ct]))+buildLiftShowsPrec gen show1Cls showCls ordCls appendId loc wrappedTy f =+ case (geStock1 gen, tyConAppTyCon_maybe realF) of+ (Just st1Tc, Just fTc) -> do+ let liftSpSel = classMethod "liftShowsPrec" show1Cls+ showsPrecSel = classMethod "showsPrec" showCls+ gtSel = classMethod ">" ordCls+ fixed = tyConAppArgs realF+ dcons = tyConDataCons fTc+ showSTy = mkVisFunTyMany stringTy stringTy+ coAt t = coDown1 gen st1Tc wrappedTy f realF t+ cons c t = mkCoreConApps consDataCon [Type charTy, c, t]+ append s t = mkApps (Var appendId) [Type charTy, s, t]+ str s = unsafeTcPluginTcM (mkStringExprFS (fsLit s))+ ordIntEv <- newWanted loc (mkClassPred ordCls [intTy])+ let ordIntDict = ctEvExpr ordIntEv+ aTv <- freshTyVar "a"+ let aTy = mkTyVarTy aTv+ innerA = mkTyConApp fTc (fixed ++ [aTy])+ spTy = mkVisFunTyMany intTy (mkVisFunTyMany aTy showSTy)+ slTy = mkVisFunTyMany (mkListTy aTy) showSTy+ spId <- freshId spTy "sp" ; slId <- freshId slTy "sl"+ dId <- freshId intTy "d" ; vId <- freshId (mkAppTy wrappedTy aTy) "v"++ -- one field becomes a precedence-parameterised ShowS renderer (@p -> ShowS@):+ -- the parameter via @sp@, a constant via its own @showsPrec@, an @H a@+ -- field via @liftShowsPrec \@H sp sl@.+ let mkRenderer i ftA xi = interpField showCls show1Cls aTv aTy loc (override1Mod gen mMods i) Roles+ { onParam = \p -> mkApps (Var spId) [mkUncheckedIntExpr p, Var xi]+ , onConst = \ev t -> \p -> mkApps (Var showsPrecSel)+ [Type t, ctEvExpr ev, mkUncheckedIntExpr p, Var xi]+ , onApply = \ev m coB -> \p -> mkApps (Var liftSpSel)+ [ Type m, ctEvExpr ev, Type aTy, Var spId, Var slId+ , mkUncheckedIntExpr p, castReshape (Var xi) (coB aTy) ]+ } ftA++ mAltWss <- forM dcons \dc -> do+ let fts = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [aTy]))+ name = occNameString (getOccName dc)+ labels = map (occNameString . nameOccName . flSelector) (dataConFieldLabels dc)+ nameStr <- str name+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] fts+ rest <- freshId stringTy "r"+ gtBndr <- freshId boolTy "p"+ prec <- conPrec dc+ mRends <- sequence (zipWith3 mkRenderer [0 :: Int ..] fts xs)+ case sequence mRends of+ Nothing -> pure Nothing+ Just rends -> do+ let (renderers, wss) = unzip rends+ parenAt thr mk t =+ Case (mkApps (Var gtSel) [Type intTy, ordIntDict, Var dId, mkUncheckedIntExpr thr])+ gtBndr stringTy+ [ Alt (DataAlt falseDataCon) [] (mk t)+ , Alt (DataAlt trueDataCon) []+ (cons (mkCharExpr '(') (mk (cons (mkCharExpr ')') t))) ]+ goPrefix t = foldr (\r acc -> cons (mkCharExpr ' ') (App (r 11) acc)) t renderers+ prefixBody t = append nameStr (goPrefix t)+ body <-+ if dataConIsInfix dc+ then do+ opStr <- str (" " ++ name ++ " ")+ let [l, r] = renderers+ mk t = App (l (prec + 1)) (append opStr (App (r (prec + 1)) t))+ pure (parenAt prec mk (Var rest))+ else if not (null labels)+ then do+ openB <- str " {"; eqB <- str " = "; commaB <- str ", "; closeB <- str "}"+ lblStrs <- mapM str labels+ let recF = zip lblStrs renderers+ goRec [(lbl, r)] c = append lbl (append eqB (App (r 0) (append closeB c)))+ goRec ((lbl, r) : m) c = append lbl (append eqB (App (r 0) (append commaB (goRec m c))))+ goRec [] c = append closeB c+ recBody t = append nameStr (append openB (goRec recF t))+ pure (parenAt 10 recBody (Var rest))+ else if null xs+ then pure (append nameStr (Var rest))+ else pure (parenAt 10 prefixBody (Var rest))+ pure (Just (Alt (DataAlt dc) xs (Lam rest body), concat wss))++ case sequence mAltWss of+ Nothing -> pure Nothing+ Just altWss -> do+ let (alts, wss) = unzip altWss+ cb <- freshId innerA "cb"+ let spImpl = mkLams [aTv, spId, slId, dId, vId]+ (destructInner fTc (fixed ++ [aTy]) (Cast (Var vId) (coAt aTy)) cb showSTy alts)+ pure (Just (spImpl, mkNonCanonical ordIntEv : concat wss))+ _ -> pure Nothing+ where (realF, mMods) = peelOverride1 gen f++-- | A @Show T@ dictionary from a @showsPrec@ implementation.+mkShowDict :: Class -> Id -> Type -> CoreExpr -> TcPluginM CoreExpr+mkShowDict showCls showList__Id tT spImpl = do+ vS <- freshId tT "v" ; vL <- freshId tT "v"+ let showImpl = Lam vS (mkApps spImpl [mkUncheckedIntExpr 0, Var vS, mkNilExpr charTy])+ sp0 = Lam vL (mkApps spImpl [mkUncheckedIntExpr 0, Var vL])+ showListImpl = mkApps (Var showList__Id) [Type tT, sp0]+ pure (mkClassDict showCls tT [spImpl, showImpl, showListImpl])++-- | The quantified @Show@ superclass @forall a. Show a => Show (g a)@, from+-- @liftShowsPrec@ instantiated at @sp = showsPrec \@a@, @sl = showList \@a@.+buildQuantShow :: Class -> Id -> Type -> CoreExpr -> TcPluginM CoreExpr+buildQuantShow showCls showList__Id gTy liftSpImpl =+ buildQuantSuper showCls gTy \aTy gaTy dShowA -> do+ let spA = mkApps (Var (classMethod "showsPrec" showCls)) [Type aTy, Var dShowA]+ slA = mkApps (Var (classMethod "showList" showCls)) [Type aTy, Var dShowA]+ spGA = mkApps liftSpImpl [Type aTy, spA, slA]+ mkShowDict showCls showList__Id gaTy spGA++synthShow1 :: GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthShow1 gen show1Cls loc wrappedTy f = do+ showCls <- lookupOrig gHC_INTERNAL_SHOW (mkTcOcc "Show") >>= tcLookupClass+ ordCls <- tcLookupClass ordClassName+ appendId <- tcLookupId appendName+ showList__Id <- lookupOrig gHC_INTERNAL_SHOW (mkVarOcc "showList__") >>= tcLookupId+ m <- buildLiftShowsPrec gen show1Cls showCls ordCls appendId loc wrappedTy f+ case m of+ Nothing -> pure Nothing+ Just (liftSpImpl, ws) -> do+ supers <- forM (classSCTheta show1Cls) \_ ->+ buildQuantShow showCls showList__Id wrappedTy liftSpImpl+ dict <- recDictWith show1Cls wrappedTy supers [(0, liftSpImpl)]+ pure (Just (EvExpr dict, ws))++synthOrd1 :: GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthOrd1 gen ord1Cls loc wrappedTy f = do+ ordCls <- tcLookupClass ordClassName+ eqCls <- tcLookupClass eqClassName+ mEq1Cls <- lookupClassMaybe "Data.Functor.Classes" "Eq1"+ case mEq1Cls of+ Nothing -> pure Nothing+ Just eq1Cls -> do+ mCmp <- buildLiftCompare gen ord1Cls ordCls loc wrappedTy f+ mEq <- buildLiftEq gen eq1Cls eqCls loc wrappedTy f+ case (mCmp, mEq) of+ (Just (liftCmpImpl, wsC), Just (liftEqImpl, wsE)) -> do+ -- the full Eq1 superclass dictionary (with its own quantified Eq super)+ eqSupers <- forM (classSCTheta eq1Cls) \_ -> buildQuantEq eqCls wrappedTy liftEqImpl+ let eq1Dict = mkClassDict eq1Cls wrappedTy (eqSupers ++ [liftEqImpl])+ -- Ord1's superclasses, in declaration order: the plain @Eq1 f@ and the+ -- quantified @forall a. Ord a => Ord (f a)@.+ supers <- forM (classSCTheta ord1Cls) \p ->+ if isClassPred p+ then pure eq1Dict+ else buildQuantOrd ordCls eqCls wrappedTy liftCmpImpl liftEqImpl+ dict <- recDictWith ord1Cls wrappedTy supers [(0, liftCmpImpl)]+ pure (Just (EvExpr dict, wsC ++ wsE))+ _ -> pure Nothing++-- ----- Read1 --------------------------------------------------------------++-- | Build @liftReadPrec@'s body @\@a rp rl -> ...@ for @Stock1 F@, by reusing+-- the shared GHC-faithful @readPrec@ assembler ('buildReadPrecBody'): the+-- parameter field reads with the supplied @rp@, a constant field with its own+-- @readPrec@, and an @H a@ field with @liftReadPrec \@H rp rl@ (cast back to the+-- real field type when @Override1@ reshapes the functor).+buildLiftReadPrec :: GenEnv -> Class -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (CoreExpr, [Ct]))+buildLiftReadPrec gen read1Cls readCls loc wrappedTy f =+ case (geStock1 gen, tyConAppTyCon_maybe realF) of+ (Just st1Tc, Just fTc) -> do+ (env, monadCt) <- lookupReadPrecEnv loc+ let liftRpSel = classMethod "liftReadPrec" read1Cls+ readPrecSel = classMethod "readPrec" readCls+ fixed = tyConAppArgs realF+ dcons = tyConDataCons fTc+ coAt t = coDown1 gen st1Tc wrappedTy f realF t+ rpcOf t = mkTyConApp (rpReadPrecTc env) [t]+ aTv <- freshTyVar "a"+ let aTy = mkTyVarTy aTv+ innerA = mkTyConApp fTc (fixed ++ [aTy])+ gaTy = mkAppTy wrappedTy aTy+ toWrapped e = Cast e (mkSymCo (coAt aTy))+ rpId <- freshId (rpcOf aTy) "rp"+ rlId <- freshId (rpcOf (mkListTy aTy)) "rl"+ -- each field's raw reader, plus the coercion casting the read type back to+ -- the real field type (Refl unless Override1 reshaped an @H a@ field).+ let mkFieldReader i ftA = interpField readCls read1Cls aTv aTy loc (override1Mod gen mMods i) Roles+ { onParam = (aTy, Var rpId, mkReflCo Representational aTy)+ , onConst = \ev t -> (t, mkApps (Var readPrecSel) [Type t, ctEvExpr ev], mkReflCo Representational t)+ , onApply = \ev m coB ->+ ( mkAppTy m aTy+ , mkApps (Var liftRpSel) [Type m, ctEvExpr ev, Type aTy, Var rpId, Var rlId]+ , if isReflCo (coB aTy) then mkReflCo Representational ftA else mkSymCo (coB aTy) )+ } ftA+ mConsWss <- forM dcons \dc -> do+ let fts = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [aTy]))+ mRdrs <- zipWithM mkFieldReader [0 :: Int ..] fts+ case sequence mRdrs of+ Nothing -> pure Nothing+ Just trips -> let (rdrs, wss) = unzip trips in pure (Just (dc, rdrs, concat wss))+ case sequence mConsWss of+ Nothing -> pure Nothing+ Just cons -> do+ let consForAsm = [ (dc, [ (ty, rd) | (ty, rd, _) <- rdrs ]) | (dc, rdrs, _) <- cons ]+ castMap = [ (getUnique dc, [ co | (_, _, co) <- rdrs ]) | (dc, rdrs, _) <- cons ]+ mkConVal dc argIds =+ let castCos = fromJust (lookup (getUnique dc) castMap)+ in toWrapped (conAppAt innerA dc (zipWith (\a c -> castInto (Var a) c) argIds castCos))+ body <- buildReadPrecBody env gaTy mkConVal consForAsm+ let liftRpImpl = mkLams [aTv, rpId, rlId] body+ pure (Just (liftRpImpl, monadCt : concatMap (\(_, _, w) -> w) cons))+ _ -> pure Nothing+ where (realF, mMods) = peelOverride1 gen f++-- | A @Read T@ dictionary from a @readPrec@ implementation (other methods come+-- from the class defaults via a recursive dictionary).+mkReadDict :: Class -> Type -> CoreExpr -> TcPluginM CoreExpr+mkReadDict readCls tT rpImpl = recDictWith readCls tT [] [(2, rpImpl)]++-- | The quantified @Read@ superclass @forall a. Read a => Read (g a)@, from+-- @liftReadPrec@ instantiated at @rp = readPrec \@a@, @rl = readListPrec \@a@.+buildQuantRead :: Class -> Type -> CoreExpr -> TcPluginM CoreExpr+buildQuantRead readCls gTy liftRpImpl =+ buildQuantSuper readCls gTy \aTy gaTy dReadA -> do+ let rpA = mkApps (Var (classMethod "readPrec" readCls)) [Type aTy, Var dReadA]+ rlpA = mkApps (Var (classMethod "readListPrec" readCls)) [Type aTy, Var dReadA]+ rpGA = mkApps liftRpImpl [Type aTy, rpA, rlpA]+ mkReadDict readCls gaTy rpGA++synthRead1 :: GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthRead1 gen read1Cls loc wrappedTy f = do+ readCls <- lookupOrig gHC_INTERNAL_READ (mkTcOcc "Read") >>= tcLookupClass+ m <- buildLiftReadPrec gen read1Cls readCls loc wrappedTy f+ case m of+ Nothing -> pure Nothing+ Just (liftRpImpl, ws) -> do+ supers <- forM (classSCTheta read1Cls) \_ -> buildQuantRead readCls wrappedTy liftRpImpl+ dict <- recDictWith read1Cls wrappedTy supers [(2, liftRpImpl)]+ pure (Just (EvExpr dict, ws))+
+ plugin/Stock/Compat.hs view
@@ -0,0 +1,44 @@+{-# LANGUAGE CPP #-}++-- | Cross-version shims for GHC API names that moved or were renamed.+--+-- GHC 9.10 split @base@ into @ghc-internal@, renaming the wired-in module+-- constants from @gHC_*@ to @gHC_INTERNAL_*@. We expose the 9.10+ spelling+-- everywhere and map it back to the old names on 9.8.+module Stock.Compat+ ( gHC_INTERNAL_SHOW+ , gHC_INTERNAL_READ+ , gHC_INTERNAL_LIST+ , gHC_INTERNAL_GENERICS+ , tEXT_READPREC+ , tEXT_READ_LEX+ ) where++import GHC.Unit.Types (Module, mkModule, moduleUnit)+import GHC.Unit.Module (mkModuleName)++#if MIN_VERSION_ghc(9,10,0)+import GHC.Builtin.Names+ ( gHC_INTERNAL_SHOW, gHC_INTERNAL_READ+ , gHC_INTERNAL_LIST, gHC_INTERNAL_GENERICS )+#else+import GHC.Builtin.Names (gHC_SHOW, gHC_READ, gHC_LIST, gHC_GENERICS)++gHC_INTERNAL_SHOW, gHC_INTERNAL_READ, gHC_INTERNAL_LIST, gHC_INTERNAL_GENERICS :: Module+gHC_INTERNAL_SHOW = gHC_SHOW+gHC_INTERNAL_READ = gHC_READ+gHC_INTERNAL_LIST = gHC_LIST+gHC_INTERNAL_GENERICS = gHC_GENERICS+#endif++-- @Text.ParserCombinators.ReadPrec@ and @Text.Read.Lex@ are not wired in; build+-- them in the same unit as @GHC.Read@ (base on <9.10, ghc-internal after), where+-- they moved under the @GHC.Internal.@ prefix alongside it.+tEXT_READPREC, tEXT_READ_LEX :: Module+#if MIN_VERSION_ghc(9,10,0)+tEXT_READPREC = mkModule (moduleUnit gHC_INTERNAL_READ) (mkModuleName "GHC.Internal.Text.ParserCombinators.ReadPrec")+tEXT_READ_LEX = mkModule (moduleUnit gHC_INTERNAL_READ) (mkModuleName "GHC.Internal.Text.Read.Lex")+#else+tEXT_READPREC = mkModule (moduleUnit gHC_INTERNAL_READ) (mkModuleName "Text.ParserCombinators.ReadPrec")+tEXT_READ_LEX = mkModule (moduleUnit gHC_INTERNAL_READ) (mkModuleName "Text.Read.Lex")+#endif
+ plugin/Stock/Derive.hs view
@@ -0,0 +1,369 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE AllowAmbiguousTypes #-}+{-# LANGUAGE DerivingVia #-}+{-# OPTIONS_GHC -Wno-x-partial #-} -- 'prodCon' head: guarded by the product contract+{-# OPTIONS_GHC -Wno-incomplete-uni-patterns #-} -- 'classDict' always returns @EvExpr@++-- | The Stock extension SDK.+--+-- A /synthesizer/ for a class @Cls@ is a function @Class -> Datatype+-- -> Synth EvTerm@: given a structural view of the wrapped type,+-- build the class dictionary as Core — the same static, zero-cost+-- evidence the built-in synthesizers produce (no @Generic@, no+-- runtime @Rep@).+--+-- The only non-trivial primitive a synthesizer needs beyond the+-- structure is 'field': request the dictionary for a field's type and+-- continue with its evidence. This is the \"continuation\" that lets+-- a synthesizer pause, have GHC solve a sub-constraint, and resume —+-- so @Eq@ (consumer), @Functor@ (transformer) and @Arbitrary@+-- (producer) are all just monadic programs over the same structure.+--+-- Companion packages register support for a new class by writing an+-- instance of 'DeriveStock'; the plugin discovers and runs it. This+-- module deliberately depends only on @ghc@ + @base@, so companions+-- stay light.+module Stock.Derive+ ( -- * Structural view+ Datatype(..)+ , Constructor(..)+ -- * The synthesis monad+ , Synth+ , runSynth+ , synthTc+ , liftTc+ , field+ , fresh+ , castInto+ , classDict+ , classDictWith+ , classMethod+ -- * SOP-style sum-of-products combinators (generics-sop flavour)+ , productCon+ , matchSOP+ , injectSOP+ , fromProduct+ , toProduct+ , pureFields+ , cpureFields+ , mapFields+ , cmapFields+ , zipFields+ , czipFields+ , foldlFields+ , cfoldlFields+ , traverseFields+ , ctraverseFields+ -- * The witness interface+ , Deriver(..)+ , DeriveStock(..)+ , Deriver1(..)+ , DeriveStock1(..)+ , Deriver2(..)+ , DeriveStock2(..)+ ) where++import GHC.Plugins+import GHC.Tc.Plugin (TcPluginM, newWanted, unsafeTcPluginTcM, tcLookupId)+import GHC.Tc.Types.Constraint (Ct, mkNonCanonical, ctEvExpr)+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence (EvTerm(EvExpr))+import GHC.Core.Predicate (mkClassPred)+import GHC.Core.Class (Class, classMethods, classOpItems)+import GHC.Types.Fixity (Fixity)+import Control.Monad (forM, foldM)+import Stock.Trans (ReaderT(..), WriterT(..))+-- The kinds for the witness-class indices. (GHC.Plugins' unqualified @Type@ is+-- the compiler's AST type; here we need the /kind/ @Data.Kind.Type@.)+import qualified Data.Kind as K++-- ---------------------------------------------------------------------------+-- Structural view of the wrapped type+-- ---------------------------------------------------------------------------++-- | What a synthesizer sees when solving @C (Stock T)@: the via-target it is+-- building the instance for (@Stock T@), the underlying analysed type (@T@),+-- the newtype-unwrap coercion between them, and @T@'s constructors. Field+-- types are already instantiated at @T@'s actual arguments.+data Datatype = Datatype+ { dtVia :: Type -- ^ the via-target, e.g. @Stock T@ — what the instance is /for/+ , dtUnwrap :: Coercion -- ^ @dtVia ~R dtType@ (newtype unwrap)+ , dtType :: Type -- ^ the underlying type, e.g. @T@ (or @T a@)+ , dtCons :: [Constructor]+ }++-- | One constructor of the analysed type: its 'DataCon', field types+-- (instantiated at @T@'s arguments), fixity, and record labels if any.+data Constructor = Constructor+ { conDataCon :: DataCon+ , conFields :: [Type] -- ^ field types the synthesizer sees, instantiated at+ -- @T@'s arguments — the /modifier/ types where a field+ -- is overridden (see "Stock.Override"), else the real ones+ , conFixity :: Fixity -- ^ for infix constructors (default @defaultFixity@)+ , conLabels :: Maybe [FieldLabel] -- ^ record selectors, if a record+ , conFieldCos :: [Coercion] -- ^ per field, @realFieldType ~R conFields!!i@; 'Refl' when+ -- the field is not overridden. 'matchSOP'\/'injectSOP'+ -- apply these so synthesizers see only 'conFields'.+ }++-- ---------------------------------------------------------------------------+-- The synthesis monad: a writer of emitted wanteds over a reader of the CtLoc+-- ---------------------------------------------------------------------------++-- | A Core-building computation that may request sub-instances (emitting+-- wanted constraints) and allocate fresh binders. Structurally this is a+-- reader of the 'CtLoc' over a writer of emitted 'Ct's over 'TcPluginM', so we+-- derive the monad instances straight from that stack (the representations are+-- coercible).+newtype Synth a = Synth (CtLoc -> TcPluginM (a, [Ct]))+ deriving (Functor, Applicative, Monad)+ via ReaderT CtLoc (WriterT [Ct] TcPluginM)++-- | Run a synthesizer at a constraint location, collecting the wanteds it+-- emitted (to be returned to GHC alongside the solution).+runSynth :: CtLoc -> Synth a -> TcPluginM (a, [Ct])+runSynth loc (Synth g) = g loc++-- | Build a @Synth@ from a location-dependent, wanted-emitting action — the+-- inverse of 'runSynth'. Lets a raw @CtLoc -> TcPluginM (EvTerm, [Ct])@+-- synthesizer be presented as a @Deriver@ (see @viaSynth@ in the plugin).+synthTc :: (CtLoc -> TcPluginM (a, [Ct])) -> Synth a+synthTc = Synth++-- | Lift a plugin action.+liftTc :: TcPluginM a -> Synth a+liftTc m = Synth \_ -> do a <- m; pure (a, [])++-- | The continuation: request the dictionary for @C ty@ and resume with its+-- evidence. Emits the wanted so GHC solves it (possibly via this very plugin,+-- enabling recursion into the field's own instance).+field :: Class -> Type -> Synth CoreExpr+field cls ty = Synth \loc -> do+ ev <- newWanted loc (mkClassPred cls [ty])+ pure (ctEvExpr ev, [mkNonCanonical ev])++-- | A fresh local binder of the given type.+fresh :: Type -> String -> Synth Id+fresh ty s = liftTc $ do+ u <- unsafeTcPluginTcM getUniqueM+ pure (mkLocalId (mkSystemName u (mkVarOcc s)) manyDataConTy ty)++-- | A class method selected by its source name — order-independent, unlike+-- indexing @classMethods@ positionally (whose order can differ across GHC+-- versions). Panics if the class has no such method (a plugin bug).+classMethod :: String -> Class -> Id+classMethod name cls =+ case filter ((== name) . occNameString . occName) (classMethods cls) of+ (m : _) -> m+ [] -> pprPanic "stock: classMethod: no method" (text name <+> ppr cls)++-- | Apply a class's dictionary constructor: @C:Cls \@ty m1 .. mn@.+classDict :: Class -> Type -> [CoreExpr] -> EvTerm+classDict cls ty methods =+ EvExpr (mkApps (Var (dataConWorkId (classDataCon cls))) (Type ty : methods))++-- | Build a (recursive) dictionary giving explicit superclass dictionaries and+-- implementations for the listed method indices; every other method is taken+-- from the class's own default (applied to the recursive dictionary). Lets a+-- synthesizer fill a many-method class from a few key methods — e.g.+-- @Hashable@ from just @hashWithSalt@ (its @hash@ has a default), with its+-- @Eq@ superclass supplied as @[field eqCls ty]@.+classDictWith :: Class -> Type -> [CoreExpr] -> [(Int, CoreExpr)] -> Synth EvTerm+classDictWith cls ty supers overrides = do+ dvar <- fresh (mkClassPred cls [ty]) "dict"+ methods <- forM (zip [0 ..] (classMethods cls)) \(i, _) ->+ case lookup i overrides of+ Just e -> pure e+ Nothing -> case snd (classOpItems cls !! i) of+ Just (nm, _) -> do dm <- liftTc (tcLookupId nm)+ pure (mkApps (Var dm) [Type ty, Var dvar])+ Nothing -> error "stock: classDictWith: method has no default and no override"+ let EvExpr con = classDict cls ty (supers ++ methods)+ pure (EvExpr (Let (Rec [(dvar, con)]) (Var dvar)))++-- ---------------------------------------------------------------------------+-- SOP-style sum-of-products combinators+-- ---------------------------------------------------------------------------+--+-- A thin @generics-sop@ flavour over the structural view. The correspondence:+--+-- @+-- generics-sop role Stock.Derive+-- ──────────── ──── ────────────+-- from x (NS elim, ∃) sum dispatch matchSOP dt r x (\\i con fs -> …)+-- to . inj (NS intro) sum build injectSOP dt con fs+-- cpure_NP (NP, Π) field tabulate cpureFields C k con+-- hcmap (NP) field map cmapFields C k con xs+-- cliftA2_NP (NP) field zip czipFields C k con xs ys+-- hcfoldl (NP) field collapse cfoldlFields C step z con xs+-- @+--+-- The split mirrors @SOP f = NS (NP f)@: 'matchSOP'\/'injectSOP' handle the+-- /sum/ (the @NS@, an existential over constructors); the @cpure@\/@cmap@\/+-- @czip@\/@cfoldl@ family handle one constructor's /product/ (its @NP@, the+-- representable @Fin n -> f@). Because the @NP@ combinators take a+-- @Constructor@, they compose inside 'matchSOP' for any constructor of a sum,+-- not just the sole product one. The @All c xs@ constraint is implicit: the+-- @c@-prefixed combinators call 'field' per field for the @cls@ dictionary.+-- So @eqDeriver@ (sum), @semigroupDeriver@\/@monoidDeriver@ (product) and+-- companions like @stock-hashable@\/@NFData@ read like their generic kin.++-- | The constructor of a product (the sole one). Exported so product+-- synthesizers can feed it to the per-@Constructor@ NP combinators below.+productCon :: Datatype -> Constructor+productCon = head . dtCons++-- | The SOP eliminator (@from@ + @case@): scrutinise a value of the via-type+-- and dispatch on its constructor. @k idx con fields@ builds the @resTy@-typed+-- branch body for constructor @con@ (index @idx@ in 'dtCons') with its bound+-- field expressions. One alternative per constructor — so this is the+-- sum-of-products generalisation of 'fromProduct'.+matchSOP :: Datatype -> Type -> CoreExpr+ -> (Int -> Constructor -> [CoreExpr] -> Synth CoreExpr) -> Synth CoreExpr+matchSOP dt resTy v k = do+ cb <- fresh (dtType dt) "s"+ alts <- forM (zip [0 ..] (dtCons dt)) \(i, c) -> do+ -- bind each pattern var at the /real/ field type (the coercion's LHS),+ -- then present the continuation the value coerced to the modifier type.+ xs <- mapM (\co -> fresh (coercionLKind co) "x") (conFieldCos c)+ body <- k i c (zipWith castInto (map Var xs) (conFieldCos c))+ pure (Alt (DataAlt (conDataCon c)) xs body)+ pure (Case (Cast v (dtUnwrap dt)) cb resTy alts)++-- | @x |> co@, skipping the cast entirely when @co@ is reflexive (the+-- not-overridden case) so the generated Core stays byte-identical.+castInto :: CoreExpr -> Coercion -> CoreExpr+castInto e co = if isReflCo co then e else Cast e co++-- | The SOP introducer (@inj@ + @to@): build a value of the via-type from a+-- chosen constructor and one expression per its fields.+injectSOP :: Datatype -> Constructor -> [CoreExpr] -> CoreExpr+injectSOP dt c es =+ Cast (mkCoreConApps (conDataCon c) (map Type (tyConAppArgs (dtType dt)) ++ es'))+ (mkSymCo (dtUnwrap dt))+ where -- each result comes back at the modifier type; coerce it to the real+ -- field type before reapplying the constructor (no-op when reflexive).+ es' = zipWith (\e co -> castInto e (mkSymCo co)) es (conFieldCos c)++-- | @productTypeFrom@ + a continuation: the single-constructor case of+-- 'matchSOP'.+fromProduct :: Datatype -> Type -> CoreExpr -> ([CoreExpr] -> Synth CoreExpr)+ -> Synth CoreExpr+fromProduct dt resTy v k = matchSOP dt resTy v \_ _ fields -> k fields++-- | @productTypeTo@: the single-constructor case of 'injectSOP'.+toProduct :: Datatype -> [CoreExpr] -> CoreExpr+toProduct dt = injectSOP dt (productCon dt)++-- The NP combinators below all operate on ONE @Constructor@ (≅ one @NP@), so+-- they compose directly inside 'matchSOP' for /any/ constructor of a sum — not+-- just the sole product one. An @NP@ is the representable functor @Fin n ->+-- f@: 'pureFields' tabulates it, 'mapFields'\/'zipFields' act positionwise, and+-- 'foldlFields' collapses it. Each has a @c@onstrained sibling that requests+-- the field's @cls@ dictionary (the implicit @All c xs@).++-- | @pure_NP@ \/ @cpure_NP@: one result per field, produced from its type+-- (@cpure@ also from its @cls@ dictionary, e.g. @k ft d = mempty \@ft d@).+pureFields :: (Type -> Synth a) -> Constructor -> Synth [a]+pureFields k con = mapM k (conFields con)++-- | 'pureFields' that also hands each field its own @cls@ dictionary.+cpureFields :: Class -> (Type -> CoreExpr -> CoreExpr) -> Constructor -> Synth [CoreExpr]+cpureFields cls k = pureFields \ft -> do d <- field cls ft; pure (k ft d)++-- | @hmap@ \/ @hcmap@: map over the fields positionwise (the basic @NP@ action;+-- @cmap@ also hands each field's @cls@ dictionary to the step).+mapFields :: (Type -> CoreExpr -> Synth a) -> Constructor -> [CoreExpr] -> Synth [a]+mapFields k con xs = sequence (zipWith k (conFields con) xs)++-- | 'mapFields' that also hands each field its own @cls@ dictionary.+cmapFields :: Class -> (Type -> CoreExpr -> CoreExpr -> CoreExpr) -> Constructor -> [CoreExpr] -> Synth [CoreExpr]+cmapFields cls k = mapFields \ft x -> do d <- field cls ft; pure (k ft d x)++-- | @liftA2_NP@ \/ @cliftA2_NP@: combine two field-lists positionwise (@czip@+-- via each field's @cls@ dictionary, e.g. @k ft d x y = (\<>) \@ft d x y@).+zipFields :: (Type -> CoreExpr -> CoreExpr -> Synth a)+ -> Constructor -> [CoreExpr] -> [CoreExpr] -> Synth [a]+zipFields k con xs ys = sequence (zipWith3 k (conFields con) xs ys)++-- | 'zipFields' that also hands each field its own @cls@ dictionary.+czipFields :: Class -> (Type -> CoreExpr -> CoreExpr -> CoreExpr -> CoreExpr)+ -> Constructor -> [CoreExpr] -> [CoreExpr] -> Synth [CoreExpr]+czipFields cls k = zipFields \ft x y -> do d <- field cls ft; pure (k ft d x y)++-- | @hfoldl@ \/ @cfoldl_NP@: collapse the fields left-to-right through an+-- accumulator @a@ (any host value — a 'CoreExpr', a list, …). The step of the+-- unconstrained 'foldlFields' has full @Synth@ access (request any/no/several+-- dictionaries); 'cfoldlFields' hands it each field's @cls@ dictionary. Needed+-- by accumulating classes, e.g. @Hashable@'s @hashWithSalt@ threading the salt.+foldlFields :: (a -> Type -> CoreExpr -> Synth a) -> a -> Constructor -> [CoreExpr] -> Synth a+foldlFields step z con fields =+ foldM (\acc (ft, e) -> step acc ft e) z (zip (conFields con) fields)++-- | 'foldlFields' that also hands each field its own @cls@ dictionary.+cfoldlFields :: Class+ -> (CoreExpr -> Type -> CoreExpr -> CoreExpr -> Synth CoreExpr) -- ^ @acc ft dict field@+ -> CoreExpr -- ^ initial accumulator+ -> Constructor -> [CoreExpr] -> Synth CoreExpr+cfoldlFields cls step =+ foldlFields \acc ft e -> do d <- field cls ft; step acc ft d e++-- | @htraverse@ over a list of fields: produce one @a@ per field in @Synth@.+-- The most general traversal-shaped combinator — used for applicative-effectful+-- work like generating `Gen a` values (for @Arbitrary@). 'traverseFields' has+-- full @Synth@ access; 'ctraverseFields' hands each field's @cls@ dictionary+-- to the step.+traverseFields :: (Type -> CoreExpr -> Synth a) -> Constructor -> [CoreExpr] -> Synth [a]+traverseFields k con xs = sequence (zipWith k (conFields con) xs)++-- | @hctraverse@: the @c@onstrained 'traverseFields' — requests each field's+-- @cls@ dictionary and hands it to the step (alongside the field value).+-- Used by @Arbitrary@ (request `Arbitrary ft` per field, emit `Gen ft`),+-- @CoArbitrary@ (request `CoArbitrary ft`, emit function generator),+-- and @Shrink@ (request `Shrink ft`, emit shrink list).+ctraverseFields :: Class+ -> (Type -> CoreExpr -> CoreExpr -> Synth CoreExpr) -- ^ @ft dict field@+ -> Constructor -> [CoreExpr] -> Synth [CoreExpr]+ctraverseFields cls k = traverseFields \ft e -> do d <- field cls ft; k ft d e++-- ---------------------------------------------------------------------------+-- The witness interface+-- ---------------------------------------------------------------------------++-- | A class's synthesizer, keyed by the wrapper arity it works through.+newtype Deriver = Deriver { runDeriver :: Class -> Datatype -> Synth EvTerm }++-- | Register synthesis of a class @cls@ derived @via Stock@. The method does+-- not mention @cls@, so the plugin selects the instance by looking it up in the+-- instance environment rather than by ordinary dispatch.+class DeriveStock (cls :: K.Type -> K.Constraint) where+ deriveStock :: Deriver++-- | A @Stock1@ synthesizer for a @(Type -> Type)@ class: given the class, the+-- constraint location, the via-target @Stock1 F@ and the inner @F@, build the+-- dictionary — or 'Nothing' if a field shape is unsupported. (Lifted classes+-- need the parameter-variance walk, so they get the raw form rather than the+-- 'Datatype'-based 'Deriver'; the @Stock1@ 'TyCon' is recoverable as+-- @tyConAppTyCon@ of the via-target.)+newtype Deriver1 = Deriver1+ { runDeriver1 :: Class -> CtLoc -> Type -> Type -> TcPluginM (Maybe (EvTerm, [Ct])) }++-- | Register synthesis of a @(Type -> Type)@ class derived @via Stock1@ (the+-- lifted counterpart of 'DeriveStock' — e.g. @NFData1@, @Hashable1@).+class DeriveStock1 (cls :: (K.Type -> K.Type) -> K.Constraint) where+ deriveStock1 :: Deriver1++-- | The @Stock2@ analogue of 'Deriver1': given the class, the location, the+-- via-target @Stock2 P@ and the inner @P@, build the dictionary.+newtype Deriver2 = Deriver2+ { runDeriver2 :: Class -> CtLoc -> Type -> Type -> TcPluginM (Maybe (EvTerm, [Ct])) }++-- | Register synthesis of a @(Type -> Type -> Type)@ class derived @via Stock2@+-- (e.g. @NFData2@).+class DeriveStock2 (cls :: (K.Type -> K.Type -> K.Type) -> K.Constraint) where+ deriveStock2 :: Deriver2
+ plugin/Stock/Enum.hs view
@@ -0,0 +1,343 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE DerivingVia #-}+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns -Wno-unused-imports #-}+-- | @Enum@ and @Ix@ synthesizers. @Enum@ is for enumerations (all-nullary+-- constructors); its @toEnum@ range-checks like GHC. @Ix@ covers both+-- enumerations ('synthIx') and single-constructor products ('synthIxProduct',+-- Cartesian range \/ mixed-radix index). (@Bounded@ lives in "Stock.Bounded".)+module Stock.Enum where+-- Most names below (data-con/type builders, coercion builders, occ-name+-- helpers, …) are re-exported by 'GHC.Plugins', so we only import explicitly+-- the ones it does not provide.+import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Tc.Utils.Monad (addErrTc)+import GHC.Tc.Errors.Types (mkTcRnUnknownMessage)+import GHC.Types.Error (mkPlainError, noHints)+import GHC.Core.Class (Class, className, classMethods, classOpItems, classTyCon)+import GHC.Core.Predicate (classifyPredType, Pred(ClassPred), mkClassPred)+import GHC.Builtin.Types.Prim (intPrimTy)+import GHC.Builtin.PrimOps (PrimOp(TagToEnumOp))+import GHC.Core.Make (mkRuntimeErrorApp, pAT_ERROR_ID)+import GHC.Builtin.PrimOps.Ids (primOpId)+import GHC.Builtin.Names ( eqClassName, ordClassName, appendName+ , enumClassName, mapName, numClassName+ , enumFromToName, enumFromThenToName+ , eqStringName+ , genClassName, repTyConName, u1TyConName, k1TyConName+ , prodTyConName, sumTyConName+ , monoidClassName, foldableClassName, functorClassName+ , semigroupClassName )+import Stock.Compat ( gHC_INTERNAL_SHOW, gHC_INTERNAL_READ+ , gHC_INTERNAL_LIST, gHC_INTERNAL_GENERICS )+import GHC.Core.Reduction (mkReduction)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import GHC.Rename.Fixity (lookupFixityRn)+import GHC.Types.Fixity (Fixity(..), defaultFixity)+import GHC.Core.TyCo.Compare (eqType)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.SimpleOpt (defaultSimpleOpts)+import GHC.Core.Unfold.Make (mkInlineUnfoldingWithArity)+import GHC.Core.InstEnv (classInstances, is_dfun, is_tys)+import GHC.Runtime.Loader (getValueSafely)+import Stock.Derive+import Data.Maybe (catMaybes, fromJust, isJust, fromMaybe)+import qualified Data.Monoid as Mon (Alt(..)) -- 'Alt' clashes with GHC.Core's case-alt 'Alt'+import Stock.Trans (MaybeT(..))+import Control.Monad (forM, zipWithM, unless, guard)+import Data.IORef (IORef, newIORef, readIORef, modifyIORef')+import Stock.Internal+import Stock.Ord++-- | A constructor's fixity precedence (default 9), used for @Show@/@Read@ of+-- infix constructors (@showParen (d > prec)@, args at @prec+1@).+synthEnum :: Class -> CtLoc -> Type -> Type -> Coercion -> [(DataCon, [Coercion])]+ -> TcPluginM (EvTerm, [Ct])+synthEnum cls loc wrappedTy innerTy co dcons0 = do+ ordCls <- tcLookupClass ordClassName+ mapId <- tcLookupId mapName+ eftId <- tcLookupId enumFromToName -- enumFromTo (class method)+ efttId <- tcLookupId enumFromThenToName -- enumFromThenTo (class method)+ let dcons = map fst dcons0 -- enumerations have no fields to override+ tagToEnumId = primOpId TagToEnumOp+ geSel = classMethod ">=" ordCls -- (>=)+ maxTag = mkUncheckedIntExpr (fromIntegral (length dcons - 1))+ toWrapped e = Cast e (mkSymCo co)+ fromInner v = Cast (Var v) co++ enumIntEv <- newWanted loc (mkClassPred cls [intTy])+ ordIntEv <- newWanted loc (mkClassPred ordCls [intTy])+ let enumIntDict = ctEvExpr enumIntEv+ ordIntDict = ctEvExpr ordIntEv++ -- fromEnum v = <tag of v>+ fv <- freshId wrappedTy "v"+ fcb <- freshId innerTy "cb"+ let fromEnumImpl = mkLams [fv] $+ Case (fromInner fv) fcb intTy+ [ Alt (DataAlt dc) [] (mkUncheckedIntExpr (fromIntegral i))+ | (i, dc) <- zip [0 :: Int ..] dcons ]++ -- toEnum i: GHC's derived toEnum RANGE-CHECKS and errors when out of range.+ -- Without the check, @tagToEnum#@ on a bad tag is undefined behaviour (it+ -- segfaults), so we replicate the guard: @if 0 <= i && i <= maxTag then+ -- tagToEnum# i else error@.+ ti <- freshId intTy "i"+ tcb <- freshId intTy "ib"+ tip <- freshId intPrimTy "i#"+ bLo <- freshId boolTy "blo"+ bHi <- freshId boolTy "bhi"+ let leSel = classMethod "<=" ordCls+ okCon = Case (Var ti) tcb wrappedTy+ [ Alt (DataAlt intDataCon) [tip]+ (toWrapped (mkApps (Var tagToEnumId) [Type innerTy, Var tip])) ]+ errOut = mkRuntimeErrorApp pAT_ERROR_ID wrappedTy+ "toEnum: argument out of range (derived via Stock)"+ toEnumImpl = mkLams [ti] $+ Case (mkApps (Var geSel) [Type intTy, ordIntDict, Var ti, mkUncheckedIntExpr 0]) bLo wrappedTy+ [ Alt (DataAlt falseDataCon) [] errOut+ , Alt (DataAlt trueDataCon) []+ (Case (mkApps (Var leSel) [Type intTy, ordIntDict, Var ti, maxTag]) bHi wrappedTy+ [ Alt (DataAlt falseDataCon) [] errOut+ , Alt (DataAlt trueDataCon) [] okCon ]) ]++ -- enumFrom x = map toEnum (enumFromTo (fromEnum x) maxTag)+ ex <- freshId wrappedTy "x"+ let mapToCon es = mkApps (Var mapId) [Type intTy, Type wrappedTy, toEnumImpl, es]+ enumFromImpl = mkLams [ex] $ mapToCon $+ mkApps (Var eftId) [Type intTy, enumIntDict, mkApps fromEnumImpl [Var ex], maxTag]++ -- enumFromThen x y = map toEnum (enumFromThenTo (fromEnum x) (fromEnum y) lim)+ -- where lim = if fromEnum y >= fromEnum x then maxTag else 0+ etx <- freshId wrappedTy "x"+ ety <- freshId wrappedTy "y"+ lbn <- freshId boolTy "b"+ let fx = mkApps fromEnumImpl [Var etx]+ fy = mkApps fromEnumImpl [Var ety]+ lim = Case (mkApps (Var geSel) [Type intTy, ordIntDict, fy, fx]) lbn intTy+ [ Alt (DataAlt falseDataCon) [] (mkUncheckedIntExpr 0)+ , Alt (DataAlt trueDataCon) [] maxTag ]+ enumFromThenImpl = mkLams [etx, ety] $ mapToCon $+ mkApps (Var efttId) [Type intTy, enumIntDict, fx, fy, lim]++ -- succ / pred / enumFromTo / enumFromThenTo via class defaults (recursive dict)+ dmSucc <- defMethId cls 0+ dmPred <- defMethId cls 1+ dmEFT <- defMethId cls 6+ dmEFTT <- defMethId cls 7+ dict <- recClassDict cls wrappedTy \dvar ->+ let useDef dm = mkApps (Var dm) [Type wrappedTy, Var dvar]+ in pure [ useDef dmSucc, useDef dmPred+ , toEnumImpl, fromEnumImpl+ , enumFromImpl, enumFromThenImpl+ , useDef dmEFT, useDef dmEFTT ]+ pure (EvExpr dict, [mkNonCanonical enumIntEv, mkNonCanonical ordIntEv])++-- | Synthesize an @Ix@ dictionary for an enumeration. @range@/@unsafeIndex@/+-- @inRange@ work on constructor tags; @index@/@rangeSize@/@unsafeRangeSize@+-- come from the class defaults; the @Ord@ superclass is synthesized too.+synthIx :: Class -> CtLoc -> Type -> Type -> Coercion -> [(DataCon, [Coercion])]+ -> TcPluginM (EvTerm, [Ct])+synthIx cls loc wrappedTy innerTy co dcons0 = do+ ordCls <- tcLookupClass ordClassName+ numCls <- tcLookupClass numClassName+ enumCls <- tcLookupClass enumClassName+ mapId <- tcLookupId mapName+ eftId <- tcLookupId enumFromToName+ let dcons = map fst dcons0 -- enumerations have no fields to override+ tagToEnumId = primOpId TagToEnumOp+ leSel = classMethod "<=" ordCls -- (<=)+ subSel = classMethod "-" numCls -- (-)+ pairTy = mkBoxedTupleTy [wrappedTy, wrappedTy]+ tupCon = tupleDataCon Boxed 2+ toWrapped e = Cast e (mkSymCo co)+ fromInner v = Cast (Var v) co++ enumIntEv <- newWanted loc (mkClassPred enumCls [intTy])+ ordIntEv <- newWanted loc (mkClassPred ordCls [intTy])+ numIntEv <- newWanted loc (mkClassPred numCls [intTy])+ let enumIntDict = ctEvExpr enumIntEv+ ordIntDict = ctEvExpr ordIntEv+ numIntDict = ctEvExpr numIntEv++ -- tag function (fromEnum) and tagToEnum (toEnum), as in synthEnum+ fv <- freshId wrappedTy "v"; fcb <- freshId innerTy "cb"+ let fromEnumImpl = mkLams [fv] $ Case (fromInner fv) fcb intTy+ [ Alt (DataAlt dc) [] (mkUncheckedIntExpr (fromIntegral i))+ | (i, dc) <- zip [0 :: Int ..] dcons ]+ tagOf e = mkApps fromEnumImpl [e]+ ti <- freshId intTy "i"; tcb <- freshId intTy "ib"; tip <- freshId intPrimTy "i#"+ let toEnumImpl = mkLams [ti] $ Case (Var ti) tcb wrappedTy+ [ Alt (DataAlt intDataCon) [tip]+ (toWrapped (mkApps (Var tagToEnumId) [Type innerTy, Var tip])) ]++ -- range (l,u) = map toEnum (enumFromTo (tag l) (tag u))+ rlu <- freshId pairTy "lu"; rcb <- freshId pairTy "cb"+ rl <- freshId wrappedTy "l"; ru <- freshId wrappedTy "u"+ let rangeImpl = mkLams [rlu] $ Case (Var rlu) rcb (mkListTy wrappedTy)+ [ Alt (DataAlt tupCon) [rl, ru]+ (mkApps (Var mapId) [Type intTy, Type wrappedTy, toEnumImpl,+ mkApps (Var eftId) [Type intTy, enumIntDict, tagOf (Var rl), tagOf (Var ru)]]) ]++ -- unsafeIndex (l,u) i = tag i - tag l+ ulu <- freshId pairTy "lu"; ucb <- freshId pairTy "cb"+ ul <- freshId wrappedTy "l"; uu <- freshId wrappedTy "u"; ui <- freshId wrappedTy "i"+ let unsafeIndexImpl = mkLams [ulu, ui] $ Case (Var ulu) ucb intTy+ [ Alt (DataAlt tupCon) [ul, uu]+ (mkApps (Var subSel) [Type intTy, numIntDict, tagOf (Var ui), tagOf (Var ul)]) ]++ -- inRange (l,u) i = tag l <= tag i && tag i <= tag u+ ilu <- freshId pairTy "lu"; icb <- freshId pairTy "cb"+ il <- freshId wrappedTy "l"; iu <- freshId wrappedTy "u"; ii <- freshId wrappedTy "i"+ ib <- freshId boolTy "b"+ let le a b = mkApps (Var leSel) [Type intTy, ordIntDict, a, b]+ inRangeImpl = mkLams [ilu, ii] $ Case (Var ilu) icb boolTy+ [ Alt (DataAlt tupCon) [il, iu]+ (Case (le (tagOf (Var il)) (tagOf (Var ii))) ib boolTy+ [ Alt (DataAlt falseDataCon) [] (Var (dataConWorkId falseDataCon))+ , Alt (DataAlt trueDataCon) [] (le (tagOf (Var ii)) (tagOf (Var iu))) ]) ]++ ordSuper <- unwrapEv . fst <$> synthOrd ordCls loc wrappedTy innerTy co dcons0+ dmIndex <- defMethId cls 1+ dmRSize <- defMethId cls 4+ dmURSize <- defMethId cls 5+ dict <- recClassDict cls wrappedTy \dvar ->+ let useDef dm = mkApps (Var dm) [Type wrappedTy, Var dvar]+ in pure [ ordSuper+ , rangeImpl, useDef dmIndex, unsafeIndexImpl, inRangeImpl+ , useDef dmRSize, useDef dmURSize ]+ pure (EvExpr dict, map mkNonCanonical [enumIntEv, ordIntEv, numIntEv])++-- | Synthesize @Ix (Stock P)@ for a single-constructor PRODUCT (like GHC):+-- @range@ is the Cartesian product of the per-field ranges (row-major nested+-- @concatMap@\/@map@), @unsafeIndex@ the mixed-radix index+-- (@acc * unsafeRangeSize fj + unsafeIndex fj@), @inRange@ the conjunction of+-- per-field @inRange@. @index@\/@rangeSize@\/@unsafeRangeSize@ come from the+-- class defaults; the @Ord@ superclass is synthesized.+synthIxProduct :: Class -> CtLoc -> Type -> Type -> Coercion -> [(DataCon, [Coercion])]+ -> TcPluginM (EvTerm, [Ct])+synthIxProduct cls loc wrappedTy innerTy co dcons0 = do+ ordCls <- tcLookupClass ordClassName+ numCls <- tcLookupClass numClassName+ mapId <- tcLookupId mapName+ concatMapId <- lookupOrig gHC_INTERNAL_LIST (mkVarOcc "concatMap") >>= tcLookupId+ let dc = fst (head dcons0)+ fts = fieldTysAt innerTy dc+ rangeSel = classMethod "range" cls+ uIndexSel = classMethod "unsafeIndex" cls+ inRangeSel = classMethod "inRange" cls+ uRSizeSel = classMethod "unsafeRangeSize" cls+ mulSel = classMethod "*" numCls+ addSel = classMethod "+" numCls+ pairW = mkBoxedTupleTy [wrappedTy, wrappedTy]+ tup2 = tupleDataCon Boxed 2+ listW = mkListTy wrappedTy+ toWrapped e = Cast e (mkSymCo co)+ fromInner e = Cast e co+ conApp args = toWrapped (conAppAt innerTy dc args)+ fieldEvs <- mapM (\ft -> newWanted loc (mkClassPred cls [ft])) fts+ numIntEv <- newWanted loc (mkClassPred numCls [intTy])+ let dicts = map ctEvExpr fieldEvs+ numIntDict = ctEvExpr numIntEv+ pairOf ft l u = mkCoreConApps tup2 [Type ft, Type ft, l, u] -- (l,u)::(ft,ft)+ rangeFE ft d l u = mkApps (Var rangeSel) [Type ft, d, pairOf ft l u]+ uIdxFE ft d l u i = mkApps (Var uIndexSel) [Type ft, d, pairOf ft l u, i]+ inRngFE ft d l u i = mkApps (Var inRangeSel) [Type ft, d, pairOf ft l u, i]+ uRSzFE ft d l u = mkApps (Var uRSizeSel) [Type ft, d, pairOf ft l u]+ mul a b = mkApps (Var mulSel) [Type intTy, numIntDict, a, b]+ add a b = mkApps (Var addSel) [Type intTy, numIntDict, a, b]++ -- destructure a @wrappedTy@ bound into its field binders, wrapping a body+ let destr v binders resTy body = do+ cb <- freshId innerTy "cb"+ pure (Case (fromInner (Var v)) cb resTy [Alt (DataAlt dc) binders body])++ -- range (lo,hi) = [ P x.. | xj <- range (lj,uj) ] (nested concatMap/map)+ luR <- freshId pairW "lu"; lcb <- freshId pairW "lcb"+ loR <- freshId wrappedTy "lo"; hiR <- freshId wrappedTy "hi"+ lsR <- mapM (`freshId` "l") fts; usR <- mapM (`freshId` "u") fts+ let mkRange [] chosen = pure (mkListExpr wrappedTy [conApp (map Var chosen)])+ mkRange [(ft, d, l, u)] chosen = do+ x <- freshId ft "x"+ pure (mkApps (Var mapId) [Type ft, Type wrappedTy+ , Lam x (conApp (map Var (chosen ++ [x]))), rangeFE ft d (Var l) (Var u)])+ mkRange ((ft, d, l, u) : r) chosen = do+ x <- freshId ft "x"+ bd <- mkRange r (chosen ++ [x])+ pure (mkApps (Var concatMapId) [Type ft, Type wrappedTy, Lam x bd, rangeFE ft d (Var l) (Var u)])+ rangeInner <- mkRange (zip4 fts dicts lsR usR) []+ rangeUs <- destr hiR usR listW rangeInner+ rangeLs <- destr loR lsR listW rangeUs+ let rangeImpl = mkLams [luR] $ Case (Var luR) lcb listW+ [ Alt (DataAlt tup2) [loR, hiR] rangeLs ]++ -- unsafeIndex (lo,hi) i = mixed-radix: foldl (\a (l,u,i) -> a*urs(l,u) + uidx(l,u) i) 0+ luI <- freshId pairW "lu"; icb <- freshId pairW "icb"; iV <- freshId wrappedTy "i"+ loI <- freshId wrappedTy "lo"; hiI <- freshId wrappedTy "hi"+ lsI <- mapM (`freshId` "l") fts; usI <- mapM (`freshId` "u") fts; isI <- mapM (`freshId` "i") fts+ let idxBody = foldl (\acc (ft, d, l, u, i) -> add (mul acc (uRSzFE ft d (Var l) (Var u)))+ (uIdxFE ft d (Var l) (Var u) (Var i)))+ (mkUncheckedIntExpr 0) (zipWith5q fts dicts lsI usI isI)+ idxIs <- destr iV isI intTy idxBody+ idxUs <- destr hiI usI intTy idxIs+ idxLs <- destr loI lsI intTy idxUs+ let uIndexImpl = mkLams [luI, iV] $ Case (Var luI) icb intTy+ [ Alt (DataAlt tup2) [loI, hiI] idxLs ]+ -- note: iV is the second lambda arg; destr on iV is inside (uses iV bound above)++ -- inRange (lo,hi) i = and [ inRange (lj,uj) ij ]+ luN <- freshId pairW "lu"; ncb <- freshId pairW "ncb"; nV <- freshId wrappedTy "i"+ loN <- freshId wrappedTy "lo"; hiN <- freshId wrappedTy "hi"+ lsN <- mapM (`freshId` "l") fts; usN <- mapM (`freshId` "u") fts; isN <- mapM (`freshId` "i") fts+ let conj [] = pure (Var (dataConWorkId trueDataCon))+ conj ((ft, d, l, u, i) : more) = do+ b <- freshId boolTy "b"+ rest <- conj more+ pure (Case (inRngFE ft d (Var l) (Var u) (Var i)) b boolTy+ [ Alt (DataAlt falseDataCon) [] (Var (dataConWorkId falseDataCon))+ , Alt (DataAlt trueDataCon) [] rest ])+ inRBody <- conj (zipWith5q fts dicts lsN usN isN)+ inRIs <- destr nV isN boolTy inRBody+ inRUs <- destr hiN usN boolTy inRIs+ inRLs <- destr loN lsN boolTy inRUs+ let inRangeImpl = mkLams [luN, nV] $ Case (Var luN) ncb boolTy+ [ Alt (DataAlt tup2) [loN, hiN] inRLs ]++ (ordEv, ordWs) <- synthOrd ordCls loc wrappedTy innerTy co dcons0+ let ordSuper = unwrapEv ordEv+ dmIndex <- defMethId cls 1+ dmRSize <- defMethId cls 4+ dmURSize <- defMethId cls 5+ dict <- recClassDict cls wrappedTy \dvar ->+ let useDef dm = mkApps (Var dm) [Type wrappedTy, Var dvar]+ in pure [ ordSuper, rangeImpl, useDef dmIndex, uIndexImpl, inRangeImpl+ , useDef dmRSize, useDef dmURSize ]+ pure (EvExpr dict, map mkNonCanonical (fieldEvs ++ [numIntEv]) ++ ordWs)++-- 4-/5-way zips into tuples (local; avoid Data.List name clutter)+zip4 :: [a] -> [b] -> [c] -> [d] -> [(a, b, c, d)]+zip4 (a:as) (b:bs) (c:cs) (d:ds) = (a,b,c,d) : zip4 as bs cs ds+zip4 _ _ _ _ = []+zipWith5q :: [Type] -> [CoreExpr] -> [Id] -> [Id] -> [Id] -> [(Type, CoreExpr, Id, Id, Id)]+zipWith5q (a:as) (b:bs) (c:cs) (d:ds) (e:es) = (a,b,c,d,e) : zipWith5q as bs cs ds es+zipWith5q _ _ _ _ _ = []++-- | Synthesize a @Read@ dictionary for prefix (non-record, non-infix)+-- constructors, mirroring the Report's derived @readsPrec@:+--+-- readsPrec d = foldr (++) [] [ readParen (paren K) (parse K) | K <- cons ]+-- parse K r = [ (K a1..an, rn) | (tok,r1) <- lex r, tok == "K"+-- , (a1,r2) <- readsPrec 11 r1, ... ]+--+-- @readList@/@readPrec@/@readListPrec@ come from the class default methods via+-- a recursive dictionary, so @read@ (which goes through @readPrec@) works too.
+ plugin/Stock/Eq.hs view
@@ -0,0 +1,154 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE DerivingVia #-}+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns -Wno-unused-imports #-}+-- | @Eq@ synthesizer: two values are equal iff same constructor and all fields equal.+module Stock.Eq where+-- Most names below (data-con/type builders, coercion builders, occ-name+-- helpers, …) are re-exported by 'GHC.Plugins', so we only import explicitly+-- the ones it does not provide.+import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Tc.Utils.Monad (addErrTc)+import GHC.Tc.Errors.Types (mkTcRnUnknownMessage)+import GHC.Types.Error (mkPlainError, noHints)+import GHC.Core.Class (Class, className, classMethods, classOpItems, classTyCon)+import GHC.Core.Predicate (classifyPredType, Pred(ClassPred), mkClassPred)+import GHC.Builtin.Types.Prim (intPrimTy)+import GHC.Builtin.PrimOps (PrimOp(TagToEnumOp))+import GHC.Builtin.PrimOps.Ids (primOpId)+import GHC.Builtin.Names ( eqClassName, ordClassName, appendName+ , enumClassName, mapName, numClassName+ , enumFromToName, enumFromThenToName+ , eqStringName+ , genClassName, repTyConName, u1TyConName, k1TyConName+ , prodTyConName, sumTyConName+ , monoidClassName, foldableClassName, functorClassName+ , semigroupClassName )+import Stock.Compat ( gHC_INTERNAL_SHOW, gHC_INTERNAL_READ+ , gHC_INTERNAL_LIST, gHC_INTERNAL_GENERICS )+import GHC.Core.Reduction (mkReduction)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import GHC.Rename.Fixity (lookupFixityRn)+import GHC.Types.Fixity (Fixity(..), defaultFixity)+import GHC.Core.TyCo.Compare (eqType)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.SimpleOpt (defaultSimpleOpts)+import GHC.Core.Unfold.Make (mkInlineUnfoldingWithArity)+import GHC.Core.InstEnv (classInstances, is_dfun, is_tys)+import GHC.Runtime.Loader (getValueSafely)+import Stock.Derive+import Data.Maybe (catMaybes, fromJust, isJust, fromMaybe)+import qualified Data.Monoid as Mon (Alt(..)) -- 'Alt' clashes with GHC.Core's case-alt 'Alt'+import Stock.Trans (MaybeT(..))+import Control.Monad (forM, zipWithM, unless, guard)+import Data.IORef (IORef, newIORef, readIORef, modifyIORef')+import Stock.Internal++-- @(==)@ is the SOP eliminator twice over: dispatch @a@, then @b@; equal+-- constructors conjoin their per-field @(==)@s (each field's @Eq@ a wanted),+-- mismatched constructors are @False@. @(/=)@ negates @(==)@.+eqDeriver :: Deriver+eqDeriver = Deriver \cls dt -> do+ let via = dtVia dt+ eqSel = classMethod "==" cls+ true_ = Var (dataConWorkId trueDataCon)+ false_ = Var (dataConWorkId falseDataCon)+ -- x0==y0 && x1==y1 && … , short-circuiting via nested case; the last+ -- field is the bare comparison (as @&&@ and stock @deriving@ produce).+ eqField (ft, x, y) = do d <- field cls ft -- the continuation: get Eq ft+ pure (mkApps (Var eqSel) [Type ft, d, x, y])+ conjEq [] = pure true_+ conjEq [t] = eqField t+ conjEq (t : rest) = do+ e <- eqField t+ restE <- conjEq rest+ scr <- fresh boolTy "c"+ pure (Case e scr boolTy+ [ Alt (DataAlt falseDataCon) [] false_+ , Alt (DataAlt trueDataCon) [] restE ])+ aId <- fresh via "a"+ bId <- fresh via "b"+ body <- matchSOP dt boolTy (Var aId) \i ci xs ->+ matchSOP dt boolTy (Var bId) \j _ ys ->+ if i == j then conjEq (zip3 (conFields ci) xs ys) else pure false_+ let eqImpl = mkLams [aId, bId] body+ na <- fresh via "a" ; nb <- fresh via "b" ; ns <- fresh boolTy "c"+ let neqImpl = mkLams [na, nb] $+ Case (mkApps eqImpl [Var na, Var nb]) ns boolTy+ [ Alt (DataAlt falseDataCon) [] true_+ , Alt (DataAlt trueDataCon) [] false_ ]+ pure (classDict cls via [eqImpl, neqImpl])++-- | Pointwise @Semigroup@ for a single-constructor product: @C x.. \<\> C y.. =+-- C (x \<\> y)..@, each field combined with its own @(\<\>)@ (a wanted). Same+-- result as @Generically@, synthesized statically (a \"faster Generically\").+-- @sconcat@\/@stimes@ come from the class defaults.+synthEq :: Class -> CtLoc -> Type -> Type -> Coercion -> [(DataCon, [Coercion])]+ -> TcPluginM (EvTerm, [Ct])+synthEq cls loc wrappedTy innerTy co dcons = do+ let true_ = Var (dataConWorkId trueDataCon)+ false_ = Var (dataConWorkId falseDataCon)+ scrut v = Cast (Var v) co -- (v |> co) :: innerTy+ indexed = zip [0 :: Int ..] dcons+ realFts dc = fieldTysAt innerTy dc -- field's real (bind) type++ aId <- freshId wrappedTy "a"+ bId <- freshId wrappedTy "b"++ -- case (a|>co) of { Ci x.. -> case (b|>co) of { Cj y.. -> body i j } }+ outer <- forM indexed \(i, (dci, cosI)) -> do+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] (realFts dci)+ inner <- forM indexed \(j, (dcj, _)) -> do+ ys <- zipWithM (\n ft -> freshId ft ("y" ++ show n)) [0 :: Int ..] (realFts dcj)+ if i == j+ then do+ (body, ws) <- conj loc (zip3 xs ys cosI)+ pure (Alt (DataAlt dcj) ys body, ws)+ else pure (Alt (DataAlt dcj) ys false_, [])+ innerBndr <- freshId innerTy "cb"+ let (ialts, iws) = unzip inner+ pure (Alt (DataAlt dci) xs (Case (scrut bId) innerBndr boolTy ialts), concat iws)++ outerBndr <- freshId innerTy "ca"+ let (oalts, ows) = unzip outer+ eqImpl = mkLams [aId, bId] (Case (scrut aId) outerBndr boolTy oalts)++ -- (/=) = \a b -> case (==) a b of { False -> True; True -> False }+ na <- freshId wrappedTy "a"+ nb <- freshId wrappedTy "b"+ ns <- freshId boolTy "c"+ let neqImpl = mkLams [na, nb] $+ Case (mkApps eqImpl [Var na, Var nb]) ns boolTy+ [ Alt (DataAlt falseDataCon) [] true_+ , Alt (DataAlt trueDataCon) [] false_ ]+ dict = mkClassDict cls wrappedTy [eqImpl, neqImpl]+ pure (EvExpr dict, concat ows)++-- | Conjoin per-field equalities — @and [x0 == y0, x1 == y1, …]@ — via 'andE'+-- (the short-circuiting @&&@ chain). Each field's @Eq@ dictionary is a wanted.+-- Each triple is @(x, y, fieldCo)@; the field is compared at its modifier type+-- (@coercionRKind fieldCo@, the real type when 'Refl'), the bound values coerced.+conj :: CtLoc -> [(Id, Id, Coercion)] -> TcPluginM (CoreExpr, [Ct])+conj loc triples = do+ eqCls <- tcLookupClass eqClassName+ let eqSel = classMethod "==" eqCls -- (==)+ evs <- mapM (\(_, _, fco) -> newWanted loc (mkClassPred eqCls [coercionRKind fco])) triples+ let cmp ((x, y, fco), ev) = mkApps (Var eqSel)+ [Type (coercionRKind fco), ctEvExpr ev, castInto (Var x) fco, castInto (Var y) fco]+ body <- andE (map cmp (zip triples evs))+ pure (body, map mkNonCanonical evs)++-- | Synthesize a full @Ord (Stock Inner)@ dictionary for any single-level+-- algebraic type: tag order between constructors, lexicographic within. Every+-- comparison is derived from a single @compare@. Returns the field @Ord@ and+-- @Eq@-superclass wanteds.
+ plugin/Stock/Functor.hs view
@@ -0,0 +1,323 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE DerivingVia #-}+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns -Wno-unused-imports #-}+-- | @Functor@ \/ @Contravariant@ and @Foldable@ synthesizers over @Stock1@ (the variance walk).+module Stock.Functor where+-- Most names below (data-con/type builders, coercion builders, occ-name+-- helpers, …) are re-exported by 'GHC.Plugins', so we only import explicitly+-- the ones it does not provide.+import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Tc.Utils.Monad (addErrTc)+import GHC.Tc.Errors.Types (mkTcRnUnknownMessage)+import GHC.Types.Error (mkPlainError, noHints)+import GHC.Core.Class (Class, className, classMethods, classOpItems, classTyCon)+import GHC.Core.Predicate (classifyPredType, Pred(ClassPred), mkClassPred)+import GHC.Builtin.Types.Prim (intPrimTy)+import GHC.Builtin.PrimOps (PrimOp(TagToEnumOp))+import GHC.Builtin.PrimOps.Ids (primOpId)+import GHC.Builtin.Names ( eqClassName, ordClassName, appendName+ , enumClassName, mapName, numClassName+ , enumFromToName, enumFromThenToName+ , eqStringName+ , genClassName, repTyConName, u1TyConName, k1TyConName+ , prodTyConName, sumTyConName+ , monoidClassName, foldableClassName, functorClassName+ , semigroupClassName )+import Stock.Compat ( gHC_INTERNAL_SHOW, gHC_INTERNAL_READ+ , gHC_INTERNAL_LIST, gHC_INTERNAL_GENERICS )+import GHC.Core.Reduction (mkReduction)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import GHC.Rename.Fixity (lookupFixityRn)+import GHC.Types.Fixity (Fixity(..), defaultFixity)+import GHC.Core.TyCo.Compare (eqType)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.SimpleOpt (defaultSimpleOpts)+import GHC.Core.Unfold.Make (mkInlineUnfoldingWithArity)+import GHC.Core.InstEnv (classInstances, is_dfun, is_tys)+import GHC.Runtime.Loader (getValueSafely)+import Stock.Derive+import Data.Maybe (catMaybes, fromJust, isJust, fromMaybe)+import qualified Data.Monoid as Mon (Alt(..)) -- 'Alt' clashes with GHC.Core's case-alt 'Alt'+import Stock.Trans (MaybeT(..))+import Control.Monad (forM, zipWithM, unless, guard)+import Data.List (zipWith4)+import Data.IORef (IORef, newIORef, readIORef, modifyIORef')+import Stock.Internal++synthFunctor :: GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthFunctor = synthMap1 Cov++-- | Synthesize @Contravariant (Stock1 F)@ — the contravariant instance of+-- @synthMap1@.+synthContravariant :: GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthContravariant = synthMap1 Con++-- | The shared engine for the two single-parameter map-like classes over+-- @Stock1 F@. @fmap@ and @contramap@ differ only in: the order of the two type+-- variables in the method (@forall a b@ vs @forall a' a@), the direction of the+-- supplied function (@a -> b@ vs @a' -> a@), and which 'varMap' base case it+-- feeds — so both are this one definition. The non-overridden method (@(\<$)@+-- resp. @(>$)@, both at class-method index 1) comes from the class default; the+-- field walk is the full variance recursion in 'varMap'.+synthMap1 :: Variance -> GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthMap1 dir gen cls loc wrappedTy f =+ case geStock1 gen of+ Just st1Tc+ -- peel an optional @Override1 cfg F@: @realF@ is the genuine constructor,+ -- @mMods@ the per-field functor modifiers (e.g. @[] -> ZipList@).+ | let (realF, mMods) = peelOverride1 gen f+ , Just fTc <- tyConAppTyCon_maybe realF -> do+ functorCls <- tcLookupClass functorClassName+ let isCov = case dir of Cov -> True; Con -> False+ fixed = tyConAppArgs realF+ dcons = tyConDataCons fTc+ coAt t = coDown1 gen st1Tc wrappedTy f realF t -- Stock1 (Override1? F) t ~R F t+ svTv <- freshTyVar "a" -- scrutinee param (input @f@ is at it)+ rvTv <- freshTyVar (if isCov then "b" else "a'") -- result param+ let svTy = mkTyVarTy svTv ; rvTy = mkTyVarTy rvTv+ innerS = mkTyConApp fTc (fixed ++ [svTy])+ gTy = if isCov then mkVisFunTyMany svTy rvTy -- fmap: a -> b+ else mkVisFunTyMany rvTy svTy -- contramap: a' -> a+ gId <- freshId gTy "g"+ sfId <- freshId (mkAppTy wrappedTy svTy) "sf"+ cb <- freshId innerS "cb"++ -- the only per-direction knobs: where the bare parameter maps, and whether+ -- contravariant subfields (@Pred a@) are allowed. The variance walk then+ -- handles constants, covariant functor fields, and arbitrary arrow nesting.+ let (covFwd, conFwd, mContra)+ | isCov = (Just (Var gId), Nothing, Nothing)+ | otherwise = (Nothing, Just (Var gId), Just cls)+ -- @i@/@rvFt@ let an @Override1@ modifier reshape this field's functor+ -- (@h a -> m a@), feeding @varMap@ the modifier type and bridging the+ -- field value with @realFt ~R m a@ coercions.+ mapField i x ftA rvFt = case override1Mod gen mMods i of+ Nothing -> do+ m <- varMap functorCls mContra loc svTv rvTy covFwd conFwd Cov ftA+ pure (fmap (\(e, ws) -> (App e (Var x), ws)) m)+ Just modf -> do+ let effFt = mkAppTy modf svTy -- m a+ coS = mkStockCo (PluginProv "stock") Representational ftA effFt+ coR = mkStockCo (PluginProv "stock") Representational rvFt (mkAppTy modf rvTy)+ m <- varMap functorCls mContra loc svTv rvTy covFwd conFwd Cov effFt+ pure (fmap (\(e, ws) -> (Cast (App e (Cast (Var x) coS)) (mkSymCo coR), ws)) m)+ binders = if isCov then [svTv, rvTv] else [rvTv, svTv]++ malts <- forM dcons \dc -> do+ let fts = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [svTy]))+ rvFts = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [rvTy]))+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] fts+ mfs <- sequence (zipWith4 mapField [0 :: Int ..] xs fts rvFts)+ case sequence mfs of+ Nothing -> pure Nothing+ Just pairs ->+ let (vals, wss) = unzip pairs+ body = Cast (mkCoreConApps dc (map Type (fixed ++ [rvTy]) ++ vals))+ (mkSymCo (coAt rvTy)) -- F rv -> Stock1 F rv+ in pure (Just (Alt (DataAlt dc) xs body, concat wss))++ case sequence malts of+ Nothing -> pure Nothing+ Just altWss -> do+ let (alts, wss) = unzip altWss+ methodImpl = mkLams (binders ++ [gId, sfId])+ (destructInner fTc (fixed ++ [svTy]) (Cast (Var sfId) (coAt svTy))+ cb (mkAppTy wrappedTy rvTy) alts)+ dmExtra <- defMethId cls 1 -- (<$) / (>$)+ dict <- recClassDict cls wrappedTy \dvar ->+ pure [ methodImpl, mkApps (Var dmExtra) [Type wrappedTy, Var dvar] ]+ pure (Just (EvExpr dict, concat wss))+ _ -> pure Nothing++-- | Synthesize @Foldable (Stock1 F)@. @foldMap@ maps the parameter fields and+-- folds @H a@ fields with their own @foldMap@, combining contributions with+-- @(<>)@ (constant fields contribute nothing); all other @Foldable@ methods+-- come from the class defaults. 'Nothing' for unsupported field shapes.+synthFoldable :: GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthFoldable gen foldableCls loc wrappedTy f =+ case geStock1 gen of+ Just st1Tc+ | let (realF, mMods) = peelOverride1 gen f -- @Override1@: reshape h-a fields+ , Just fTc <- tyConAppTyCon_maybe realF -> do+ monoidCls <- tcLookupClass monoidClassName+ let fixed = tyConAppArgs realF+ dcons = tyConDataCons fTc+ foldMapSel = classMethod "foldMap" foldableCls+ memptySel = classMethod "mempty" monoidCls+ mappendSel = classMethod "mappend" monoidCls+ coAt t = coDown1 gen st1Tc wrappedTy f realF t+ atv <- freshTyVar "a" ; mtv <- freshTyVar "m"+ let aTy = mkTyVarTy atv ; mTy = mkTyVarTy mtv+ innerA = mkTyConApp fTc (fixed ++ [aTy])+ dM <- freshId (mkClassPred monoidCls [mTy]) "dM"+ gId <- freshId (mkVisFunTyMany aTy mTy) "g"+ tId <- freshId (mkAppTy wrappedTy aTy) "t"+ cb <- freshId innerA "cb"+ let memptyE = mkApps (Var memptySel) [Type mTy, Var dM]+ mappendE x y = mkApps (Var mappendSel) [Type mTy, Var dM, x, y]+ -- field contribution: Nothing = unsupported; Just Nothing = omitted+ -- foldMap :: forall m a. Monoid m => ... (m is quantified first)+ foldMapOf h ev x = mkApps (Var foldMapSel)+ [Type h, ev, Type mTy, Type aTy, Var dM, Var gId, x]+ -- GHC's @ft_*@ fold over a field's structure: a constant contributes+ -- nothing; the parameter contributes @g x@; a tuple folds every+ -- component and combines with @(<>)@; a covariant @H larg@ folds via+ -- @H@'s @foldMap@ (nested @[[a]]@ ⇒ @foldMap (foldMap g)@); a function+ -- field is rejected. 'Nothing' unsupported / @Just Nothing@ no+ -- contribution / @Just (Just (e,ws))@ contributes @e@.+ foldField ft xe+ | not (atv `elemVarSet` tyCoVarsOfType ft) = pure (Just Nothing)+ | ft `eqType` aTy = pure (Just (Just (App (Var gId) xe, [])))+ | Just _ <- splitFunTy_maybe ft = pure Nothing+ | Just (tc, args) <- splitTyConApp_maybe ft+ , isTupleTyCon tc, length args >= 2 = do+ xs <- mapM (`freshId` "u") args+ rs <- zipWithM foldField args (map Var xs)+ case sequence rs of+ Nothing -> pure Nothing+ Just mcs -> do+ cb <- freshId ft "cb"+ let (es, wss) = unzip (catMaybes mcs)+ body = if null es then memptyE else foldr1 mappendE es+ pure (Just (Just ( Case xe cb mTy+ [Alt (DataAlt (tupleDataCon Boxed (length args))) xs body]+ , concat wss )))+ | Just (h, larg) <- splitAppTy_maybe ft+ , not (atv `elemVarSet` tyCoVarsOfType h) = do+ y <- freshId larg "y"+ mi <- foldField larg (Var y)+ case mi of+ Just (Just (e, w)) -> do+ ev <- newWanted loc (mkClassPred foldableCls [h])+ pure (Just (Just ( mkApps (Var foldMapSel)+ [Type h, ctEvExpr ev, Type mTy, Type larg, Var dM, Lam y e, xe]+ , mkNonCanonical ev : w )))+ _ -> pure Nothing+ | otherwise = pure Nothing+ contrib i x ftA = case override1Mod gen mMods i of+ -- Override1 reshapes the field's (one-level) functor @h a -> m a@.+ Just m -> do ev <- newWanted loc (mkClassPred foldableCls [m])+ let co = mkStockCo (PluginProv "stock") Representational ftA (mkAppTy m aTy)+ pure (Just (Just (foldMapOf m (ctEvExpr ev) (Cast (Var x) co), [mkNonCanonical ev])))+ Nothing -> foldField ftA (Var x)+ malts <- forM dcons \dc -> do+ let ftsA = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [aTy]))+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] ftsA+ mcs <- sequence (zipWith3 contrib [0 :: Int ..] xs ftsA)+ case sequence mcs of+ Nothing -> pure Nothing+ Just contribs ->+ let (es, wss) = unzip (catMaybes contribs)+ body = if null es then memptyE else foldr1 mappendE es+ in pure (Just (Alt (DataAlt dc) xs body, concat wss))+ -- @foldr@ (so @toList@\/@foldr@ do not fall back to the @Endo@-based+ -- default, which drags the @Stock1@ coercion along): synthesized to match+ -- GHC's stock derivation byte-for-byte. @foldr f z (Con .. xi ..)@ nests+ -- a contribution per field around @z@: a constant passes the accumulator+ -- through; the parameter is @f xi rest@; a covariant @H a@ field is+ -- @(\\b1 b2 -> foldr (elemFn) b2 b1) xi rest@ (GHC's flip shape), where+ -- @elemFn@ recurses for nested structure. Skipped under @Override1@+ -- (which reshapes fields and is handled only by @foldMap@).+ let foldrSel = classMethod "foldr" foldableCls+ faTv <- freshTyVar "a" ; fbTv <- freshTyVar "b"+ let faTy = mkTyVarTy faTv ; fbTy = mkTyVarTy fbTv+ ffId <- freshId (mkVisFunTyMany faTy (mkVisFunTyMany fbTy fbTy)) "f"+ fzId <- freshId fbTy "z"+ ftId <- freshId (mkAppTy wrappedTy faTy) "t"+ fcb <- freshId (mkTyConApp fTc (fixed ++ [faTy])) "cb"+ let -- element-combine function for values of type @t@ (leaves are @faTy@,+ -- folded by @ffId@): @t -> b -> b@.+ mkElemFn :: Type -> TcPluginM (Maybe (CoreExpr, [Ct]))+ mkElemFn t+ | t `eqType` faTy = pure (Just (Var ffId, []))+ | Just (h, larg) <- splitAppTy_maybe t+ , not (faTv `elemVarSet` tyCoVarsOfType h) = do+ mfn <- mkElemFn larg+ case mfn of+ Nothing -> pure Nothing+ Just (efn, w0) -> do+ ev <- newWanted loc (mkClassPred foldableCls [h])+ p <- freshId t "p" ; acc <- freshId fbTy "acc"+ let e = mkLams [p, acc] (mkApps (Var foldrSel)+ [Type h, ctEvExpr ev, Type larg, Type fbTy, efn, Var acc, Var p])+ pure (Just (e, mkNonCanonical ev : w0))+ | otherwise = pure Nothing+ -- one field's contribution wrapped around continuation @k :: b@.+ contribR :: Type -> Id -> CoreExpr -> TcPluginM (Maybe (CoreExpr, [Ct]))+ contribR ft x k+ | not (faTv `elemVarSet` tyCoVarsOfType ft) = pure (Just (k, []))+ | ft `eqType` faTy = pure (Just (mkApps (Var ffId) [Var x, k], []))+ | Just _ <- splitFunTy_maybe ft = pure Nothing+ | Just (tc, args) <- splitTyConApp_maybe ft+ , isTupleTyCon tc, length args >= 2 = do+ us <- mapM (`freshId` "u") args+ cbt <- freshId ft "ct"+ mb <- combineR (zip args us) k+ pure $ flip fmap mb \(body, w) ->+ ( Case (Var x) cbt fbTy+ [Alt (DataAlt (tupleDataCon Boxed (length args))) us body], w )+ | Just (h, larg) <- splitAppTy_maybe ft+ , not (faTv `elemVarSet` tyCoVarsOfType h) = do+ mfn <- mkElemFn larg+ case mfn of+ Nothing -> pure Nothing+ Just (efn, w0) -> do+ ev <- newWanted loc (mkClassPred foldableCls [h])+ b1 <- freshId ft "b1" ; b2 <- freshId fbTy "b2"+ let flipLam = mkLams [b1, b2] (mkApps (Var foldrSel)+ [Type h, ctEvExpr ev, Type larg, Type fbTy, efn, Var b2, Var b1])+ pure (Just (mkApps flipLam [Var x, k], mkNonCanonical ev : w0))+ | otherwise = pure Nothing+ -- nest contributions right-to-left around @z@ (= leftmost field outermost).+ combineR :: [(Type, Id)] -> CoreExpr -> TcPluginM (Maybe (CoreExpr, [Ct]))+ combineR [] k = pure (Just (k, []))+ combineR ((ft, x) : r) k = do+ mr <- combineR r k+ case mr of+ Nothing -> pure Nothing+ Just (k', w') -> do mc <- contribR ft x k'+ pure (fmap (\(e, w) -> (e, w ++ w')) mc)+ mFoldrAlts <- if isJust mMods then pure Nothing else fmap sequence $ forM dcons \dc -> do+ let ftsA = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [faTy]))+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] ftsA+ mb <- combineR (zip ftsA xs) (Var fzId)+ pure (fmap (\(body, w) -> (Alt (DataAlt dc) xs body, w)) mb)+ case sequence malts of+ Nothing -> pure Nothing+ Just altWss -> do+ let (alts, wss) = unzip altWss+ foldMapImpl = mkLams [mtv, atv, dM, gId, tId] -- forall m a. Monoid m => ...+ (destructInner fTc (fixed ++ [aTy]) (Cast (Var tId) (coAt aTy))+ cb mTy alts)+ idxOf nm = head [ i | (i, m) <- zip [0 :: Int ..] (classMethods foldableCls)+ , occNameString (occName m) == nm ]+ (foldrMethods, foldrWs) = case mFoldrAlts of+ Just altWs ->+ let (fAlts, fWss) = unzip altWs+ foldrImpl = mkLams [faTv, fbTv, ffId, fzId, ftId]+ (destructInner fTc (fixed ++ [faTy]) (Cast (Var ftId) (coAt faTy))+ fcb fbTy fAlts)+ in ([(idxOf "foldr", foldrImpl)], concat fWss)+ Nothing -> ([], [])+ dict <- recDictWith foldableCls wrappedTy []+ ((idxOf "foldMap", foldMapImpl) : foldrMethods)+ pure (Just (EvExpr dict, concat wss ++ foldrWs))+ _ -> pure Nothing++-- | Classify a field of a two-parameter type against the last two parameters+-- @a@ (first) and @b@ (second).
+ plugin/Stock/Generic.hs view
@@ -0,0 +1,387 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE DerivingVia #-}+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns -Wno-unused-imports #-}+-- | @Generic@ \/ @Generic1@ synthesizers: @Rep@ as a balanced @:+:@ \/ @:*:@ tree.+module Stock.Generic where+-- Most names below (data-con/type builders, coercion builders, occ-name+-- helpers, …) are re-exported by 'GHC.Plugins', so we only import explicitly+-- the ones it does not provide.+import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Tc.Utils.Monad (addErrTc)+import GHC.Tc.Errors.Types (mkTcRnUnknownMessage)+import GHC.Types.Error (mkPlainError, noHints)+import GHC.Core.Class (Class, className, classMethods, classOpItems, classTyCon)+import GHC.Core.Predicate (classifyPredType, Pred(ClassPred), mkClassPred)+import GHC.Builtin.Types.Prim (intPrimTy)+import GHC.Builtin.PrimOps (PrimOp(TagToEnumOp))+import GHC.Builtin.PrimOps.Ids (primOpId)+import GHC.Builtin.Names ( eqClassName, ordClassName, appendName+ , enumClassName, mapName, numClassName+ , enumFromToName, enumFromThenToName+ , eqStringName+ , genClassName, repTyConName, u1TyConName, k1TyConName+ , prodTyConName, sumTyConName+ , monoidClassName, foldableClassName, functorClassName+ , semigroupClassName )+import Stock.Compat ( gHC_INTERNAL_SHOW, gHC_INTERNAL_READ+ , gHC_INTERNAL_LIST, gHC_INTERNAL_GENERICS )+import GHC.Core.Reduction (mkReduction)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import GHC.Rename.Fixity (lookupFixityRn)+import GHC.Types.Fixity (Fixity(..), defaultFixity)+import GHC.Core.TyCo.Compare (eqType)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.SimpleOpt (defaultSimpleOpts)+import GHC.Core.Unfold.Make (mkInlineUnfoldingWithArity)+import GHC.Core.InstEnv (classInstances, is_dfun, is_tys)+import GHC.Runtime.Loader (getValueSafely)+import Stock.Derive+import Data.Maybe (catMaybes, fromJust, isJust, fromMaybe)+import qualified Data.Monoid as Mon (Alt(..)) -- 'Alt' clashes with GHC.Core's case-alt 'Alt'+import Stock.Trans (MaybeT(..))+import Control.Monad (forM, zipWithM, unless, guard)+import Data.IORef (IORef, newIORef, readIORef, modifyIORef')+import Stock.Internal++-- | Field types with @Override1@ modifiers applied: an @h a@ field whose config+-- names a modifier @m@ becomes @m a@ (so its @Rep1@ leaf is @Rec1 m@); all other+-- fields are unchanged. Used by /both/ 'rewriteRep1' (the @Rep1@ type) and+-- 'synthGeneric1' (the @from1@\/@to1@ values), keeping them in lock-step.+reshape1Ftys :: GenEnv -> Maybe [Type] -> TyVar -> Type -> [Type] -> [Type]+reshape1Ftys gen mMods atv aTy fts =+ [ case (classifyField atv aTy ft, override1Mod gen mMods i) of+ (Just (FApp _), Just m) -> mkAppTy m aTy -- @h a@ field → @m a@+ _ -> ft+ | (i, ft) <- zip [0 :: Int ..] fts ]++rewriteRep :: GenEnv -> RewriteEnv -> [Ct] -> [Type] -> TcPluginM TcPluginRewriteResult+rewriteRep gen _env _given [arg]+ -- @Rep (Stock (Override T cfg))@: the leaves carry the /modifier/ types, so+ -- @Generically (Stock (Override T cfg))@ derives over the overridden fields.+ -- (Checked first; the plain branch would otherwise treat @Override@ as a data+ -- type.) @synthGeneric@'s @from@\/@to@ coerce to match.+ | Just (realInner, cons) <- overrideFieldTypes gen arg = do+ fixOf <- mkFixOf (geMeta gen) (map fst cons)+ let struct = repMetaFts gen fixOf realInner cons+ lhs = mkTyConApp (geRepTc gen) [arg]+ co = mkStockCo (PluginProv "stock") Nominal lhs struct+ pure (TcPluginRewriteTo (mkReduction co struct) [])+ | Just repr <- mkRepr (geStock gen) arg, not (null (rCons repr)) = do+ fixOf <- mkFixOf (geMeta gen) (map ciCon (rCons repr))+ let struct = repMeta gen fixOf (rInner repr) (map ciCon (rCons repr))+ lhs = mkTyConApp (geRepTc gen) [arg]+ co = mkStockCo (PluginProv "stock") Nominal lhs struct+ pure (TcPluginRewriteTo (mkReduction co struct) [])+rewriteRep _ _ _ _ = pure TcPluginNoRewrite++-- | Rewrite @Rep1 (Stock1 F)@ to the parameter-aware structure (@Par1@\/@Rec1@\/+-- @Rec0@ leaves under the @M1@ metadata). No rewrite if any field is an+-- unsupported shape (composition etc.).+rewriteRep1 :: GenEnv -> RewriteEnv -> [Ct] -> [Type] -> TcPluginM TcPluginRewriteResult+rewriteRep1 gen _env _given args+ | (arg : _) <- reverse args -- @Rep1@ is poly-kinded: drop the kind arg+ , Just st1Tc <- geStock1 gen+ , Just stTc <- tyConAppTyCon_maybe arg, stTc == st1Tc+ , (_ : f : _) <- tyConAppArgs arg+ -- @f@ may be @Override1 cfg realF@: peel it, then reshape @h a@ fields to+ -- @m a@ so the @Rep1@ leaves use the modifier (in lock-step with 'synthGeneric1').+ , let (realF, mMods) = peelOverride1 gen f+ , Just fTc <- tyConAppTyCon_maybe realF = do+ a0 <- freshTyVar "a"+ let aT0 = mkTyVarTy a0+ fixed = tyConAppArgs realF+ dcons = tyConDataCons fTc+ innerF = mkTyConApp fTc (fixed ++ [aT0])+ ftysOf dc = reshape1Ftys gen mMods a0 aT0+ (map scaledThing (dataConInstOrigArgTys dc (fixed ++ [aT0])))+ ok = all (all (isJust . rep1Field gen a0) . ftysOf) dcons+ if not ok then pure TcPluginNoRewrite else do+ fixOf <- mkFixOf (geMeta gen) dcons+ let struct = repMetaWith gen fixOf (fromJust . rep1Field gen a0) innerF+ [ (dc, ftysOf dc) | dc <- dcons ]+ lhs = mkTyConApp (g1RepTc (geGen1 gen)) args+ co = mkStockCo (PluginProv "stock") Nominal lhs struct+ pure (TcPluginRewriteTo (mkReduction co struct) [])+rewriteRep1 _ _ _ _ = pure TcPluginNoRewrite++-- | The structural @Rep@ for a whole datatype: a single constructor is just its+-- product 'repStruct'; several constructors form a /balanced/ @:+:@ tree of+-- their product structs (mirroring GHC's @foldBal@).+-- @cons@ carries each constructor's /modifier/ field types ('ciFields') and the+-- per-cell coercions ('ciFieldCos', @realFieldType ~R modifierType@; 'Refl'+-- without an @Override@). The @Rep@ leaves use the modifier types (matching+-- 'rewriteRep'); @from@ coerces the real field /into/ the leaf, @to@ coerces+-- /back/ before rebuilding. Refl everywhere ⇒ byte-identical Core to plain.+synthGeneric :: GenEnv -> Type -> Type -> Coercion -> [ConInfo] -> TcPluginM EvTerm+synthGeneric gen wrappedTy innerTy co cons = do+ fixOf <- mkFixOf (geMeta gen) (map ciCon cons)+ let genCls = geGen gen+ k1Tc = geK1Tc gen+ prodTc = geProdTc gen ; prodDc = geProdDc gen+ sumTc = geSumTc gen+ [l1Dc, r1Dc] = tyConDataCons sumTc+ u1Dc = head (tyConDataCons (geU1Tc gen))+ rTy = geRTy gen+ kTy = liftedTypeKind+ dcons = map ciCon cons+ modFtss = map ciFields cons -- Rep leaves (modifier types)+ cosss = map ciFieldCos cons -- realFt ~R modFt per field+ realFtss = map (fieldTysAt innerTy) dcons -- bound (pattern) types+ mfcss = zipWith zip modFtss cosss -- per con: [(modFt, fco)]+ structMeta = repMetaFts gen fixOf innerTy (zip dcons modFtss) -- faithful (rewrite-target) Rep+ structBare = repData gen modFtss -- the un-M1 value structure+ lhs = mkTyConApp (geRepTc gen) [wrappedTy]+ coRep = mkStockCo (PluginProv "stock") Nominal lhs structMeta+ -- the M1 layers are newtypes, so structMeta ~R structBare (asserted, true)+ coStrip = mkStockCo (PluginProv "stock") Representational structMeta structBare++ ux <- unsafeTcPluginTcM getUniqueM+ let xtv = mkTyVar (mkSystemName ux (mkTyVarOcc "x")) liftedTypeKind+ xty = mkTyVarTy xtv+ prodTy f g = mkTyConApp prodTc [kTy, f, g]+ sumTy f g = mkTyConApp sumTc [kTy, f, g]+ -- Rep x ~R structMeta x ~R structBare x (and back)+ castDn = mkSubCo (mkAppCo coRep (mkNomReflCo xty)) -- Rep x ~R structMeta x+ `mkTransCo` mkAppCo coStrip (mkNomReflCo xty) -- structMeta x ~R structBare x+ castUp = mkSymCo castDn -- structBare x ~R Rep x+ k1Co ft = mkUnbranchedAxInstCo Representational+ (newTyConCo k1Tc) [kTy, rTy, ft, xty] [] -- K1 R ft x ~R ft+ -- from: real field value (fi :: realFt) coerced to its modifier type, into K1 modFt+ k1ValOv fco mft fi = Cast (castInto (Var fi) fco) (mkSymCo (k1Co mft))+ -- to: a K1 modFt projection back to the real field type+ unK1Ov fco mft scr = castInto (Cast scr (k1Co mft)) (mkSymCo fco)+ -- balanced product VALUE (+ its type), mirroring 'repStruct'/'foldBal'+ buildV [(v, t)] = (v, t)+ buildV vs = let (l, r) = splitAt (length vs `div` 2) vs+ (lv, lt) = buildV l ; (rv, rt) = buildV r+ in ( mkCoreConApps prodDc [Type kTy, Type lt, Type rt, Type xty, lv, rv]+ , prodTy lt rt )+ -- product value for one constructor's fields (per field: its (modFt, fco) + binder)+ prodValOf mfcs fis+ | null mfcs = mkCoreConApps u1Dc [Type kTy, Type xty]+ | otherwise = fst (buildV [ (k1ValOv fco mft fi, mkTyConApp k1Tc [kTy, rTy, mft])+ | ((mft, fco), fi) <- zip mfcs fis ])+ -- balanced @:+:@ injectors (one per constructor) + the sum type+ injectors [fts] = ([id], repStruct gen fts)+ injectors fss =+ let (l, r) = splitAt (length fss `div` 2) fss+ (li, lt) = injectors l ; (ri, rt) = injectors r+ wrapL v = mkCoreConApps l1Dc [Type kTy, Type lt, Type rt, Type xty, v]+ wrapR v = mkCoreConApps r1Dc [Type kTy, Type lt, Type rt, Type xty, v]+ in (map (wrapL .) li ++ map (wrapR .) ri, sumTy lt rt)+ (injs, _) = injectors modFtss++ -- from = /\x. \v -> case (v |> co) of Cᵢ f.. -> injᵢ <product> |> castUp+ vId <- freshId wrappedTy "v"+ cbV <- freshId innerTy "cb"+ fromAlts <- forM (zip dcons (zip3 realFtss mfcss injs)) \(dc, (realFts, mfcs, inj)) -> do+ fis <- zipWithM (\n ft -> freshId ft ("f" ++ show n)) [0 :: Int ..] realFts+ pure (Alt (DataAlt dc) fis (Cast (inj (prodValOf mfcs fis)) castUp))+ let fromImpl = Lam xtv (Lam vId+ (Case (Cast (Var vId) co) cbV (mkAppTy lhs xty) fromAlts))++ -- to = /\x. \r -> <project (r |> castDn) through :+: / :*:, rebuild Cᵢ>+ rId <- freshId (mkAppTy lhs xty) "r"+ let -- take apart a balanced :*: product (typed by the modifier types), returning the+ -- real-typed field exprs (each projected then coerced back) + a case-nesting wrapper+ destruct scr [(mft, fco)] = pure ([unK1Ov fco mft scr], id)+ destruct scr mfs = do+ let (lT, rT) = splitAt (length mfs `div` 2) mfs+ lt = repStruct gen (map fst lT) ; rt = repStruct gen (map fst rT)+ lv <- freshId (mkAppTy lt xty) "l"+ rv <- freshId (mkAppTy rt xty) "rr"+ cb <- freshId (mkAppTy (prodTy lt rt) xty) "pc"+ (lfs, lwrap) <- destruct (Var lv) lT+ (rfs, rwrap) <- destruct (Var rv) rT+ let wrap body = Case scr cb wrappedTy+ [Alt (DataAlt prodDc) [lv, rv] (lwrap (rwrap body))]+ pure (lfs ++ rfs, wrap)+ -- rebuild one constructor from its product struct+ rebuildCon scr mfs dc+ | null mfs = pure (Cast (conAppAt innerTy dc []) (mkSymCo co))+ | otherwise = do (fields, wrap) <- destruct scr mfs+ pure (wrap (Cast (conAppAt innerTy dc fields) (mkSymCo co)))+ -- project through the balanced :+: tree, rebuilding at each leaf+ destructSum scr [mfs] [dc] = rebuildCon scr mfs dc+ destructSum scr mfss dcs = do+ let h = length mfss `div` 2+ (lfs, rfs) = splitAt h mfss ; (ldc, rdc) = splitAt h dcs+ lt = repData gen (map (map fst) lfs) ; rt = repData gen (map (map fst) rfs)+ lv <- freshId (mkAppTy lt xty) "sl"+ rv <- freshId (mkAppTy rt xty) "sr"+ cb <- freshId (mkAppTy (sumTy lt rt) xty) "sc"+ lbody <- destructSum (Var lv) lfs ldc+ rbody <- destructSum (Var rv) rfs rdc+ pure (Case scr cb wrappedTy+ [ Alt (DataAlt l1Dc) [lv] lbody, Alt (DataAlt r1Dc) [rv] rbody ])+ toBody <- destructSum (Cast (Var rId) castDn) mfcss dcons+ let toImpl = Lam xtv (Lam rId toBody)++ pure $ EvExpr $ mkClassDict genCls wrappedTy [fromImpl, toImpl]++-- | Synthesize @Generic1 (Stock1 F)@: like 'synthGeneric' but the field+-- representation is parameter-aware — the last type variable @a@ becomes+-- @Par1@, @g a@ becomes @Rec1 g@, a constant becomes @Rec0@ (see 'rep1Field').+-- @from1@\/@to1@ wrap\/unwrap each field through the corresponding newtype.+-- 'Nothing' for shapes 'rep1Field' rejects (e.g. composition @f (g a)@).+synthGeneric1 :: GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthGeneric1 gen cls loc wrappedTy f =+ case (geStock1 gen, tyConAppTyCon_maybe realF) of+ (Just st1Tc, Just fTc) -> do+ functorCls <- tcLookupClass functorClassName+ let g1 = geGen1 gen+ fixed = tyConAppArgs realF+ dcons = tyConDataCons fTc+ k1Tc = geK1Tc gen ; rTy = geRTy gen ; kTy = liftedTypeKind+ par1Tc = g1Par1Tc g1 ; rec1Tc = g1Rec1Tc g1 ; compTc = g1CompTc g1+ fmapSel = classMethod "fmap" functorCls+ prodTc = geProdTc gen ; prodDc = geProdDc gen+ sumTc = geSumTc gen ; [l1Dc, r1Dc] = tyConDataCons sumTc+ u1Dc = head (tyConDataCons (geU1Tc gen))+ coAt t = coDown1 gen st1Tc wrappedTy f realF t+ atv <- freshTyVar "a"+ let aTy = mkTyVarTy atv+ innerA = mkTyConApp fTc (fixed ++ [aTy])+ prodTy a b = mkTyConApp prodTc [kTy, a, b]+ sumTy a b = mkTyConApp sumTc [kTy, a, b]+ u1Ty = mkTyConApp (geU1Tc gen) [kTy]+ par1Co = mkUnbranchedAxInstCo Representational (newTyConCo par1Tc) [aTy] []+ rec1Co h = mkUnbranchedAxInstCo Representational (newTyConCo rec1Tc) [kTy, h, aTy] []+ k1Co t = mkUnbranchedAxInstCo Representational (newTyConCo k1Tc) [kTy, rTy, t, aTy] []+ fieldsOf dc = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [aTy]))+ -- classify a field → (bare leaf type, wrap, unwrap, emitted wanteds).+ -- wrap\/unwrap are value transforms; composition emits a @Functor@+ -- wanted and uses @Comp1 . fmap innerWrap@ (matching GHC's DeriveGeneric1).+ classify1 mMod ft+ | ft `eqType` aTy =+ pure (Just (mkTyConTy par1Tc, \e -> Cast e (mkSymCo par1Co), \e -> Cast e par1Co, []))+ | not (atv `elemVarSet` tyCoVarsOfType ft) =+ pure (Just (mkTyConApp k1Tc [kTy, rTy, ft], \e -> Cast e (mkSymCo (k1Co ft)), \e -> Cast e (k1Co ft), []))+ | Just (h, larg) <- splitAppTy_maybe ft+ , not (atv `elemVarSet` tyCoVarsOfType h) =+ if larg `eqType` aTy+ -- @h a@ leaf, reshaped to @Rec1 m@ under @Override1@: wrap coerces+ -- the field value @h a ~R m a@ then into @Rec1 m a@; unwrap reverses.+ then let m = fromMaybe h mMod+ co = reshapeCo h m aTy+ in pure (Just ( mkTyConApp rec1Tc [kTy, m]+ , \e -> Cast e (co `mkTransCo` mkSymCo (rec1Co m))+ , \e -> Cast e (rec1Co m `mkTransCo` mkSymCo co), [] ))+ else do+ minner <- classify1 Nothing larg+ case minner of+ Nothing -> pure Nothing+ Just (innerRep, innerWrap, innerUnwrap, iws) -> do+ ev <- newWanted loc (mkClassPred functorCls [h])+ yId <- freshId larg "y"+ zId <- freshId (mkAppTy innerRep aTy) "z"+ let dict = ctEvExpr ev+ compTy = mkTyConApp compTc [kTy, kTy, h, innerRep]+ comp1Co = mkUnbranchedAxInstCo Representational+ (newTyConCo compTc) [kTy, kTy, h, innerRep, aTy] []+ innerAppA = mkAppTy innerRep aTy+ fmapAt aT bT fn x = mkApps (Var fmapSel) [Type h, dict, Type aT, Type bT, fn, x]+ -- Comp1 (fmap innerWrap e) :: (h :.: innerRep) a+ wrapE e = Cast (fmapAt larg innerAppA (mkLams [yId] (innerWrap (Var yId))) e)+ (mkSymCo comp1Co)+ -- fmap innerUnwrap (unComp1 e) :: h larg+ unwrapE e = fmapAt innerAppA larg (mkLams [zId] (innerUnwrap (Var zId))) (Cast e comp1Co)+ pure (Just (compTy, wrapE, unwrapE, mkNonCanonical ev : iws))+ | otherwise = pure Nothing+ classifiedM <- forM dcons \dc ->+ zipWithM (\i ft -> classify1 (override1Mod gen mMods i) ft) [0 :: Int ..] (fieldsOf dc)+ case traverse sequence classifiedM of+ Nothing -> pure Nothing+ Just classified -> do+ fixOf <- mkFixOf (geMeta gen) dcons+ let fieldWanteds = concatMap (concatMap (\(_, _, _, ws) -> ws)) classified+ leafTys con = [ lt | (lt, _, _, _) <- con ]+ bareCon con = case leafTys con of { [] -> u1Ty; lts -> foldBal prodTy lts }+ structBare = case map bareCon classified of { [s] -> s; ss -> foldBal sumTy ss }+ structMeta = repMetaWith gen fixOf (fromJust . rep1Field gen atv) innerA+ [ (dc, reshape1Ftys gen mMods atv aTy (fieldTysAt innerA dc)) | dc <- dcons ]+ lhs1 = mkTyConApp (g1RepTc g1) [liftedTypeKind, wrappedTy]+ coRep = mkStockCo (PluginProv "stock") Nominal lhs1 structMeta+ coStrip = mkStockCo (PluginProv "stock") Representational structMeta structBare+ castDn = mkSubCo (mkAppCo coRep (mkNomReflCo aTy))+ `mkTransCo` mkAppCo coStrip (mkNomReflCo aTy) -- Rep1..a ~R structBare a+ castUp = mkSymCo castDn+ -- balanced :*: VALUE for one constructor (over the leaf values/types)+ buildV [(v, t)] = (v, t)+ buildV vs = let (l, r) = splitAt (length vs `div` 2) vs+ (lv, lt) = buildV l ; (rv, rt) = buildV r+ in ( mkCoreConApps prodDc [Type kTy, Type lt, Type rt, Type aTy, lv, rv]+ , prodTy lt rt )+ prodValOf con fis+ | null con = mkCoreConApps u1Dc [Type kTy, Type aTy]+ | otherwise = fst (buildV [ (wrap (Var fi), lt) | ((lt, wrap, _, _), fi) <- zip con fis ])+ -- balanced :+: injectors, by the bare struct types+ injectors [con] = ([id], bareCon con)+ injectors cs =+ let (l, r) = splitAt (length cs `div` 2) cs+ (li, lt) = injectors l ; (ri, rt) = injectors r+ wrapL v = mkCoreConApps l1Dc [Type kTy, Type lt, Type rt, Type aTy, v]+ wrapR v = mkCoreConApps r1Dc [Type kTy, Type lt, Type rt, Type aTy, v]+ in (map (wrapL .) li ++ map (wrapR .) ri, sumTy lt rt)+ (injs, _) = injectors classified++ vId <- freshId (mkAppTy wrappedTy aTy) "v"+ cbV <- freshId innerA "cb"+ fromAlts <- forM (zip3 dcons classified injs) \(dc, con, inj) -> do+ fis <- zipWithM (\n ft -> freshId ft ("f" ++ show n)) [0 :: Int ..] (fieldsOf dc)+ pure (Alt (DataAlt dc) fis (Cast (inj (prodValOf con fis)) castUp))+ let fromImpl = Lam atv (Lam vId+ (Case (Cast (Var vId) (coAt aTy)) cbV (mkAppTy lhs1 aTy) fromAlts))++ rId <- freshId (mkAppTy lhs1 aTy) "r"+ let destruct scr [(_, _, unwrap, _)] = pure ([unwrap scr], id)+ destruct scr con = do+ let (lc, rc) = splitAt (length con `div` 2) con+ lt = bareCon lc ; rt = bareCon rc+ lv <- freshId (mkAppTy lt aTy) "l"+ rv <- freshId (mkAppTy rt aTy) "rr"+ cb <- freshId (mkAppTy (prodTy lt rt) aTy) "pc"+ (lfs, lw) <- destruct (Var lv) lc+ (rfs, rw) <- destruct (Var rv) rc+ pure (lfs ++ rfs, \body -> Case scr cb (mkAppTy wrappedTy aTy)+ [Alt (DataAlt prodDc) [lv, rv] (lw (rw body))])+ rebuildCon scr con dc+ | null con = pure (Cast (conAppAt innerA dc []) (mkSymCo (coAt aTy)))+ | otherwise = do (fields, wrap) <- destruct scr con+ pure (wrap (Cast (conAppAt innerA dc fields) (mkSymCo (coAt aTy))))+ destructSum scr [con] [dc] = rebuildCon scr con dc+ destructSum scr cs dcs = do+ let h = length cs `div` 2+ (lc, rc) = splitAt h cs ; (ldc, rdc) = splitAt h dcs+ lt = case lc of { [c] -> bareCon c; _ -> foldBal sumTy (map bareCon lc) }+ rt = case rc of { [c] -> bareCon c; _ -> foldBal sumTy (map bareCon rc) }+ lv <- freshId (mkAppTy lt aTy) "sl"+ rv <- freshId (mkAppTy rt aTy) "sr"+ cb <- freshId (mkAppTy (sumTy lt rt) aTy) "sc"+ lb <- destructSum (Var lv) lc ldc+ rb <- destructSum (Var rv) rc rdc+ pure (Case scr cb (mkAppTy wrappedTy aTy)+ [Alt (DataAlt l1Dc) [lv] lb, Alt (DataAlt r1Dc) [rv] rb])+ toBody <- destructSum (Cast (Var rId) castDn) classified dcons+ let toImpl = Lam atv (Lam rId toBody)+ -- Generic1 is poly-kinded: its dictionary constructor takes the+ -- kind argument before the type argument.+ dict = mkApps (Var (dataConWorkId (classDataCon cls)))+ [Type liftedTypeKind, Type wrappedTy, fromImpl, toImpl]+ pure (Just (EvExpr dict, fieldWanteds))+ _ -> pure Nothing+ where (realF, mMods) = peelOverride1 gen f++-- | Variance of an occurrence of the type parameter.
+ plugin/Stock/Internal.hs view
@@ -0,0 +1,1463 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE MultiWayIf #-}+{-# LANGUAGE TupleSections #-}+{-# LANGUAGE DerivingVia #-}+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns -Wno-unused-imports #-}+-- | Shared substrate for the Stock plugin: environments, the representation+-- EDSL, Core/dictionary builders, the variance walk, and the @Solver@ monoid.+module Stock.Internal (module Stock.Internal) where+-- Most names below (data-con/type builders, coercion builders, occ-name+-- helpers, …) are re-exported by 'GHC.Plugins', so we only import explicitly+-- the ones it does not provide.+import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Tc.Utils.Monad (addErrTc)+import GHC.Tc.Errors.Types (mkTcRnUnknownMessage)+import GHC.Types.Error (mkPlainError, noHints)+import GHC.Core.Class (Class, className, classMethods, classOpItems, classTyCon)+import GHC.Core.Predicate (classifyPredType, Pred(ClassPred), mkClassPred)+#if MIN_VERSION_ghc(9,14,0)+import GHC.Core.Predicate (mkReprEqPred)+#else+import GHC.Core.Predicate (mkReprPrimEqPred)+#endif+import GHC.Builtin.Types (promotedConsDataCon, promotedNilDataCon, unitTy)+import GHC.Builtin.Types.Prim (intPrimTy)+import GHC.Builtin.PrimOps (PrimOp(TagToEnumOp))+import GHC.Builtin.PrimOps.Ids (primOpId)+import GHC.Builtin.Names ( eqClassName, ordClassName, appendName+ , enumClassName, mapName, numClassName+ , enumFromToName, enumFromThenToName+ , eqStringName+ , genClassName, repTyConName, u1TyConName, k1TyConName+ , prodTyConName, sumTyConName+ , monoidClassName, foldableClassName, functorClassName+ , semigroupClassName, monadClassName )+import Stock.Compat ( gHC_INTERNAL_SHOW, gHC_INTERNAL_READ+ , gHC_INTERNAL_LIST, gHC_INTERNAL_GENERICS+ , tEXT_READPREC, tEXT_READ_LEX )+import GHC.Core.Reduction (mkReduction)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import GHC.Rename.Fixity (lookupFixityRn)+import GHC.Types.Fixity (Fixity(..), defaultFixity, FixityDirection(..))+import GHC.Types.SourceText (SourceText(NoSourceText))+import GHC.Core.DataCon (dataConSrcBangs, dataConImplBangs, HsSrcBang(..), HsImplBang(..), SrcStrictness(..), SrcUnpackedness(..))+import GHC.Core.TyCo.Compare (eqType)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.SimpleOpt (defaultSimpleOpts)+import GHC.Core.Unfold.Make (mkInlineUnfoldingWithArity)+import GHC.Core.InstEnv (classInstances, is_dfun, is_tys)+import GHC.Runtime.Loader (getValueSafely)+import Stock.Derive+import Data.Maybe (catMaybes, fromJust, isJust, fromMaybe, listToMaybe)+import Data.List (zipWith4)+import Data.Traversable (for)+import qualified Data.Monoid as Mon (Alt(..)) -- 'Alt' clashes with GHC.Core's case-alt 'Alt'+import Stock.Trans (MaybeT(..))+import Control.Monad (zipWithM, unless, guard)+import Data.IORef (IORef, newIORef, readIORef, modifyIORef')+-- | Entities looked up once for @Generic@ synthesis: the @Generic@ class, the+-- @Rep@ family, and the representation pieces @U1@, @K1@/@Rec0@ and @:*:@.+data GenEnv = GenEnv+ { geStock :: Maybe TyCon -- ^ our @Stock.Stock@ ('Nothing' if not in scope)+ , geStock1 :: Maybe TyCon -- ^ our @Stock.Stock1@+ , geStock2 :: Maybe TyCon -- ^ our @Stock.Stock2@+ , geWitness :: Maybe Class -- ^ @Stock.Derive.DeriveStock@ (for discovered derivers)+ , geGen :: Class+ , geRepTc :: TyCon+ , geU1Tc :: TyCon+ , geK1Tc :: TyCon+ , geProdTc :: TyCon+ , geProdDc :: DataCon+ , geSumTc :: TyCon -- ^ @:+:@ (for sum-type @Rep@s)+ , geMeta :: MetaEnv -- ^ @M1@ + promoted @Meta@ pieces (for metadata layers)+ , geGen1 :: Gen1Env -- ^ @Generic1@ / @Rep1@ pieces+ , geRTy :: Type -- ^ the @R@ tag (for @Rec0 = K1 R@)+ , geOverride :: Maybe TyCon -- ^ @Stock.Override.Override@ ('Nothing' if not in scope)+ , geAssign :: Maybe TyCon -- ^ @Stock.Override.(:=)@ — the config-entry marker+ , geAt :: Maybe TyCon -- ^ @Stock.Override.At@ — the positional selector marker+ , geKeep :: Maybe TyCon -- ^ @Stock.Override.Keep@ — the positional no-op (@_@) marker+ , geArrow :: Maybe TyCon -- ^ @Stock.Override.(-->)@ — the path-addressing marker+ , geWitness1 :: Maybe Class -- ^ @Stock.Derive.DeriveStock1@ (lifted discovered derivers)+ , geWitness2 :: Maybe Class -- ^ @Stock.Derive.DeriveStock2@ (bi-lifted discovered derivers)+ , geOverride2 :: Maybe TyCon -- ^ @Stock.Override.Override2@ — per-field override at the @Stock2@ level+ , geOverride1 :: Maybe TyCon -- ^ @Stock.Override.Override1@ — per-field override at the @Stock1@ level+ }++-- | The @M1@ newtype and the promoted @Meta@ pieces needed to build the+-- @D1@/@C1@/@S1@ metadata layers of a faithful (nominal) @Rep@.+data MetaEnv = MetaEnv+ { meM1 :: TyCon -- ^ @M1@+ , meD, meC, meS :: Type -- ^ the @D@\/@C@\/@S@ tags (kind @Type@)+ , meMetaData :: TyCon -- ^ promoted @'MetaData@+ , meMetaCons :: TyCon -- ^ promoted @'MetaCons@+ , meMetaSel :: TyCon -- ^ promoted @'MetaSel@+ , mePrefixI :: Type -- ^ @'PrefixI@+ , meInfixI :: TyCon -- ^ promoted @'InfixI@ (assoc → nat → FixityI)+ , meLeftAssoc, meRightAssoc, meNotAssoc :: Type -- ^ promoted @Associativity@+ , meNoUnpack, meSrcNoUnpack, meSrcUnpack :: Type -- ^ promoted @SourceUnpackedness@+ , meNoStrict, meSrcLazy, meSrcStrict :: Type -- ^ promoted @SourceStrictness@+ , meDecidedLazy, meDecidedStrict, meDecidedUnpack :: Type -- ^ promoted @DecidedStrictness@+ , meJustSym :: TyCon -- ^ promoted @'Just@ \@Symbol+ , meNothingSym :: Type -- ^ @'Nothing \@Symbol@+ }++-- | @Generic1@ entities: the class, the @Rep1@ family, and the parameter-aware+-- representation pieces @Par1@\/@Rec1@\/@(:.:)@.+data Gen1Env = Gen1Env+ { g1RepTc :: TyCon -- ^ @Rep1@+ , g1Par1Tc :: TyCon -- ^ @Par1@ (the bare parameter)+ , g1Rec1Tc :: TyCon -- ^ @Rec1@ (@g a@)+ , g1CompTc :: TyCon -- ^ @(:.:)@ (composition, @f (g a)@)+ }++-- | Plugin state: error-message dedup set + the @Generic@ entities.+data PluginState = PluginState+ { psSeen :: IORef [String]+ , psGen :: GenEnv+ }++-- | Short-circuiting conjunction of @Bool@-valued Core expressions — reads like+-- @and [b0, b1, …]@ but builds the nested @case e of { False -> False; True ->+-- … }@ chain, the same Core a derived @&&@ chain produces: no list, no+-- allocation, byte-identical to stock deriving.+andE :: [CoreExpr] -> TcPluginM CoreExpr+andE [] = pure (Var (dataConWorkId trueDataCon))+andE [a] = pure a+andE (a:as) = do+ r <- andE as+ scr <- freshId boolTy "c"+ pure (Case a scr boolTy [ Alt (DataAlt falseDataCon) [] (Var (dataConWorkId falseDataCon))+ , Alt (DataAlt trueDataCon) [] r ])++lookupTyConMaybe :: String -> String -> TcPluginM (Maybe TyCon)+lookupTyConMaybe modName occ = do+ res <- findImportedModule (mkModuleName modName) NoPkgQual+ case res of+ Found _ m -> Just <$> (lookupOrig m (mkTcOcc occ) >>= tcLookupTyCon)+ _ -> pure Nothing++-- | Look up the @M1@ + promoted @Meta@ entities for metadata layers.+lookupMetaEnv :: TcPluginM MetaEnv+lookupMetaEnv = do+ let gTc occ = lookupOrig gHC_INTERNAL_GENERICS (mkTcOcc occ) >>= tcLookupTyCon+ gDc occ = lookupOrig gHC_INTERNAL_GENERICS (mkDataOcc occ) >>= tcLookupDataCon+ promTy = fmap (mkTyConTy . promoteDataCon) . gDc+ m1 <- gTc "M1"+ dT <- mkTyConTy <$> gTc "D" ; cT <- mkTyConTy <$> gTc "C" ; sT <- mkTyConTy <$> gTc "S"+ md <- promoteDataCon <$> gDc "MetaData"+ mc <- promoteDataCon <$> gDc "MetaCons"+ ms <- promoteDataCon <$> gDc "MetaSel"+ pfx <- promTy "PrefixI"+ inI <- promoteDataCon <$> gDc "InfixI"+ la <- promTy "LeftAssociative" ; ra <- promTy "RightAssociative" ; na <- promTy "NotAssociative"+ nu <- promTy "NoSourceUnpackedness" ; snu <- promTy "SourceNoUnpack" ; su <- promTy "SourceUnpack"+ ns <- promTy "NoSourceStrictness" ; sl <- promTy "SourceLazy" ; ss <- promTy "SourceStrict"+ dl <- promTy "DecidedLazy" ; ds <- promTy "DecidedStrict" ; du <- promTy "DecidedUnpack"+ pure MetaEnv { meM1 = m1, meD = dT, meC = cT, meS = sT+ , meMetaData = md, meMetaCons = mc, meMetaSel = ms+ , mePrefixI = pfx, meInfixI = inI+ , meLeftAssoc = la, meRightAssoc = ra, meNotAssoc = na+ , meNoUnpack = nu, meSrcNoUnpack = snu, meSrcUnpack = su+ , meNoStrict = ns, meSrcLazy = sl, meSrcStrict = ss+ , meDecidedLazy = dl, meDecidedStrict = ds, meDecidedUnpack = du+ , meJustSym = promotedJustDataCon+ , meNothingSym = mkTyConApp promotedNothingDataCon [typeSymbolKind] }++-- | Look up the @Generic1@ / @Rep1@ entities.+lookupGen1Env :: TcPluginM Gen1Env+lookupGen1Env = do+ let gTc occ = lookupOrig gHC_INTERNAL_GENERICS (mkTcOcc occ) >>= tcLookupTyCon+ Gen1Env <$> gTc "Rep1" <*> gTc "Par1" <*> gTc "Rec1" <*> gTc ":.:"++-- | Look up a class by module + name, 'Nothing' if its module isn't available.+lookupClassMaybe :: String -> String -> TcPluginM (Maybe Class)+lookupClassMaybe modName occ = do+ res <- findImportedModule (mkModuleName modName) NoPkgQual+ case res of+ Found _ m -> Just <$> (lookupOrig m (mkTcOcc occ) >>= tcLookupClass)+ _ -> pure Nothing++-- | Look up a term-level identifier (a function\/value) by module + name,+-- 'Nothing' if its module isn't available — for companion derivers that need to+-- reference a library function (e.g. QuickCheck's @oneof@).+lookupIdMaybe :: String -> String -> TcPluginM (Maybe Id)+lookupIdMaybe modName occ = do+ res <- findImportedModule (mkModuleName modName) NoPkgQual+ case res of+ Found _ m -> Just <$> (lookupOrig m (mkVarOcc occ) >>= tcLookupId)+ _ -> pure Nothing++-- | Rewrite @Rep (Stock T)@ to its structural representation. The coercion is+-- a plugin-asserted univ coercion (there is no real @Generic@ axiom); the+-- @from@/@to@ we synthesize use the same assertion, so the two cohere. We only+-- handle single-constructor types (products) so far.+repData :: GenEnv -> [[Type]] -> Type+repData gen [fts] = repStruct gen fts+repData gen ftss = foldBal sumOf (map (repStruct gen) ftss) where + sumOf :: Type -> Type -> Type+ sumOf f g = mkTyConApp (geSumTc gen) [liftedTypeKind, f, g]++-- | The /faithful/ @Rep@ with metadata layers: @D1 meta (C1 meta (S1 meta Rec0+-- … :*: …) :+: …)@ — byte-identical in shape to GHC's stock @Rep@ (balanced+-- @:+:@/@:*:@, @M1@ wrappers carrying promoted @Meta@). Used as the rewrite+-- target; the value-level @from@\/@to@ build the un-@M1@ 'repData' value and+-- bridge with a representational coercion (the @M1@s are newtypes).+-- | @Rec0 t = K1 R t@ — the field representation for a constant (and for every+-- field in plain @Generic@).+rec0Of :: GenEnv -> Type -> Type+rec0Of gen t = mkTyConApp (geK1Tc gen) [liftedTypeKind, geRTy gen, t]++repMeta :: GenEnv -> (DataCon -> Type) -> Type -> [DataCon] -> Type+repMeta gen fixOf innerTy dcons =+ repMetaWith gen fixOf (rec0Of gen) innerTy [ (dc, fieldTysAt innerTy dc) | dc <- dcons ]++-- | 'repMeta' with explicit per-constructor field types — the @Generic@ leaves+-- carry these (the /modifier/ types under an @Override@, the real types+-- otherwise). Pairs with the @from@\/@to@ that 'Stock.Generic' builds.+repMetaFts :: GenEnv -> (DataCon -> Type) -> Type -> [(DataCon, [Type])] -> Type+repMetaFts gen fixOf = repMetaWith gen fixOf (rec0Of gen)++-- | 'repMeta' generalised over the per-field leaf representation: @Generic@+-- uses @Rec0@; @Generic1@ uses @Par1@\/@Rec1@\/@(:.:)@ ('rep1Field'). Each+-- constructor comes with the field types its leaves should carry.+repMetaWith :: GenEnv -> (DataCon -> Type) -> (Type -> Type) -> Type -> [(DataCon, [Type])] -> Type+repMetaWith gen fixOf leaf innerTy cons =+ d1 (metaData innerTc) (foldBal sumTy (map conRep cons)) where+ me = geMeta gen+ innerTc = tyConAppTyCon innerTy+ kTy = liftedTypeKind+ m1 i c f = mkTyConApp (meM1 me) [kTy, i, c, f]+ d1 = m1 (meD me) ; c1 = m1 (meC me) ; s1 = m1 (meS me)+ sumTy a b = mkTyConApp (geSumTc gen) [kTy, a, b]+ prodTy a b = mkTyConApp (geProdTc gen) [kTy, a, b]+ u1 = mkTyConApp (geU1Tc gen) [kTy]+ strLit = mkStrLitTy . fsLit+ boolT b = mkTyConTy (if b then promotedTrueDataCon else promotedFalseDataCon)+ metaData tc = mkTyConApp (meMetaData me)+ [ strLit (occNameString (nameOccName (tyConName tc)))+ , strLit (moduleNameString (moduleName modu))+ , strLit (unitString (moduleUnit modu))+ , boolT (isNewTyCon tc) ]+ where modu = nameModule (tyConName tc)+ -- MetaCons carries the constructor's FIXITY ('Infix assoc prec for an infix+ -- constructor, else 'PrefixI) — supplied by the (monadic) 'mkFixOf'.+ metaCons dc = mkTyConApp (meMetaCons me)+ [ strLit (occNameString (getOccName dc))+ , fixOf dc+ , boolT (not (null (dataConFieldLabels dc))) ]+ -- MetaSel carries the field's real source/decided strictness.+ metaSel mnm (suT, ssT, dsT) = mkTyConApp (meMetaSel me)+ [ maybe (meNothingSym me)+ (\nm -> mkTyConApp (meJustSym me) [typeSymbolKind, strLit nm]) mnm+ , suT, ssT, dsT ]+ -- derive (SourceUnpackedness, SourceStrictness, DecidedStrictness) from the+ -- DECIDED bang ('HsImplBang' is stable across GHC versions, unlike 'HsSrcBang'+ -- which changed shape thrice): an unannotated field is lazy; a @!@ field is+ -- source-strict + decided-strict; an UNPACK field is source-unpack (the rare+ -- explicit @~@ lazy annotation is the one case this can't tell from plain).+ selStr dc i = case if i < length implB then implB !! i else HsLazy of+ HsLazy -> (meNoUnpack me, meNoStrict me, meDecidedLazy me)+ HsStrict _ -> (meNoUnpack me, meSrcStrict me, meDecidedStrict me)+ HsUnpack _ -> (meSrcUnpack me, meSrcStrict me, meDecidedUnpack me)+ where implB = dataConImplBangs dc+ conRep (dc, fts) = c1 (metaCons dc) prod+ where labels = dataConFieldLabels dc+ nameAt i | null labels = Nothing+ | otherwise = Just (occNameString (nameOccName (flSelector (labels !! i))))+ prod = case fts of+ [] -> u1+ _ -> foldBal prodTy+ [ s1 (metaSel (nameAt i) (selStr dc i)) (leaf ft)+ | (i, ft) <- zip [0 :: Int ..] fts ]++-- 'Fixity' lost its leading 'SourceText' in GHC 9.12 (2-arg from 9.12 on).+fixityParts :: Fixity -> (Int, FixityDirection)+#if MIN_VERSION_ghc(9,12,0)+fixityParts (Fixity p d) = (p, d)+#else+fixityParts (Fixity _ p d) = (p, d)+#endif++-- | The per-constructor MetaCons fixity meta ('Infix assoc prec / 'PrefixI),+-- precomputed (it needs the renamer's fixity environment).+conFixityTy :: MetaEnv -> DataCon -> TcPluginM Type+conFixityTy me dc+ | dataConIsInfix dc = do+ fx <- unsafeTcPluginTcM (lookupFixityRn (dataConName dc))+ let (prec, dir) = fixityParts fx+ assoc = case dir of InfixL -> meLeftAssoc me; InfixR -> meRightAssoc me; InfixN -> meNotAssoc me+ pure (mkTyConApp (meInfixI me) [assoc, mkNumLitTy (fromIntegral prec)])+ | otherwise = pure (mePrefixI me)++-- | A pure fixity lookup over a fixed constructor set (for 'repMetaWith').+mkFixOf :: MetaEnv -> [DataCon] -> TcPluginM (DataCon -> Type)+mkFixOf me dcs = do+ tys <- mapM (conFixityTy me) dcs+ let m = zip (map getUnique dcs) tys+ pure (\dc -> fromMaybe (mePrefixI me) (lookup (getUnique dc) m))++-- | The structural @Rep@ type for a single constructor with the given field+-- types: @U1@ when there are no fields, otherwise a /balanced/ @:*:@ tree of+-- @Rec0 field@ (matching GHC's @foldBal@ nesting). No @M1@ metadata layers+-- yet — this is a valid representation that @Generically@ can use, just not+-- byte-identical to stock's.+repStruct :: GenEnv -> [Type] -> Type+repStruct gen [] = mkTyConApp (geU1Tc gen) [liftedTypeKind] -- U1 @Type+repStruct gen fts = foldBal prodOf (map rec0 fts) where++ rec0 t = mkTyConApp (geK1Tc gen) [liftedTypeKind, geRTy gen, t] -- K1 @Type R t+ prodOf f g = mkTyConApp (geProdTc gen) [liftedTypeKind, f, g] -- (f :*: g) @Type++-- | Classify a field for @Rep1@: the bare parameter @a@ ⇒ @Par1@; @g a@ with+-- @g@ closed ⇒ @Rec1 g@; a field without the parameter ⇒ @Rec0@ (constant).+-- 'Nothing' for shapes we don't yet handle (composition @f (g a)@, or the+-- parameter in a position other than the last argument of a closed functor).+rep1Field :: GenEnv -> TyVar -> Type -> Maybe Type+rep1Field gen aTv ft+ | ft `eqType` aTy = Just par1+ | not (aTv `elemVarSet` tyCoVarsOfType ft) = Just (rec0Of gen ft)+ | Just (h, larg) <- splitAppTy_maybe ft+ , not (aTv `elemVarSet` tyCoVarsOfType h) =+ if larg `eqType` aTy then Just (rec1 h) -- @h a@ ⇒ Rec1 h+ else comp h <$> rep1Field gen aTv larg -- @h (g..a)@ ⇒ h :.: <inner>+ | otherwise = Nothing+ where+ g1 = geGen1 gen ; kTy = liftedTypeKind ; aTy = mkTyVarTy aTv+ par1 = mkTyConTy (g1Par1Tc g1)+ rec1 h = mkTyConApp (g1Rec1Tc g1) [kTy, h]+ comp h inr = mkTyConApp (g1CompTc g1) [kTy, kTy, h, inr]++-- | A balanced binary fold (GHC's @foldBal@): splits the list in half and+-- recurses, giving @(a \`op\` b) \`op\` (c \`op\` d)@ rather than a right-nested+-- chain. Precondition: non-empty.+foldBal :: (a -> a -> a) -> [a] -> a+foldBal _ [x] = x+foldBal op xs = let (l, r) = splitAt (length xs `div` 2) xs+ in op (foldBal op l) (foldBal op r)++-- | Try to solve every wanted constraint by direct synthesis. Synthesis may+-- emit further wanted constraints (e.g. @Eq@ on a field type), which we hand+-- back to the solver alongside our solutions.+type Attempt = (Maybe (EvTerm, Ct), [Ct], [Ct])++-- ---------------------------------------------------------------------------+-- A little EDSL describing the datatype representation a Stock-wrapped type+-- exposes. Everything the synthesizers need to inspect lives here, so the+-- "is this something we can build an instance for, and what does it look like"+-- question is answered in exactly one place.+-- ---------------------------------------------------------------------------++-- | One constructor's representation: the constructor itself and its field+-- types (instantiated at the inner type's arguments).+data ConInfo = ConInfo+ { ciCon :: DataCon+ , ciFields :: [Type] -- ^ field types the synthesizer sees (modifier types if overridden)+ , ciFieldCos :: [Coercion] -- ^ per field, @realFieldType ~R ciFields!!i@ (Refl if not overridden)+ }++-- | The representation of @Stock Inner@: the inner type, the newtype-unwrapping+-- coercion @wrapped ~R inner@, and the constructors.+data Repr = Repr+ { rInner :: Type+ , rCo :: Coercion+ , rCons :: [ConInfo]+ }++-- | Recognise @Stock Inner@ where @Stock@ is exactly /our/ wrapper newtype+-- (identified by 'TyCon', not by name — so an unrelated user type called+-- @Stock@ is never touched) and @Inner@ is a concrete algebraic type, and read+-- off its representation. Returns 'Nothing' for anything we don't own or can't+-- analyse (including when our @Stock@ couldn't be located, i.e. @ourStock@ is+-- 'Nothing').+mkRepr :: Maybe TyCon -> Type -> Maybe Repr+mkRepr ourStock wrappedTy = do+ ourTc <- ourStock+ stockTc <- tyConAppTyCon_maybe wrappedTy+ guard (stockTc == ourTc)+ innerTy <- case tyConAppArgs wrappedTy of { (a:_) -> Just a; _ -> Nothing }+ innerTc <- tyConAppTyCon_maybe innerTy+ let dcons = tyConDataCons innerTc+ guard (not (null dcons))+ let co = mkUnbranchedAxInstCo Representational+ (newTyConCo stockTc) (tyConAppArgs wrappedTy) []+ cons = [ ConInfo dc fts (map mkRepReflCo fts)+ | dc <- dcons, let fts = fieldTysAt innerTy dc ]+ pure (Repr innerTy co cons)++-- | A plugin-asserted coercion (there is no real axiom; the plugin vouches for+-- the representational equality). 'mkUnivCo' gained a @[Coercion]@ dependency+-- argument in GHC 9.12, so this wrapper keeps call sites version-agnostic.+mkStockCo :: UnivCoProvenance -> Role -> Type -> Type -> Coercion+#if MIN_VERSION_ghc(9,12,0)+mkStockCo prov = mkUnivCo prov []+#else+mkStockCo = mkUnivCo+#endif++-- | The @Override(1\/2)@ field reshape coercion @h t ~R m t@ — 'Refl' when the+-- field is not overridden (@h == m@), else the plugin-asserted representational+-- equality. Shared by every synthesizer that reshapes a functor field.+reshapeCo :: Type -> Type -> Type -> Coercion+reshapeCo h m t+ | h `eqType` m = mkRepReflCo (mkAppTy h t)+ | otherwise = mkStockCo (PluginProv "stock") Representational (mkAppTy h t) (mkAppTy m t)++-- | Cast by a reshape coercion, skipping the no-op 'Refl' (so non-overridden+-- fields stay syntactically untouched and the emitted Core is byte-identical).+castReshape :: CoreExpr -> Coercion -> CoreExpr+castReshape e co = if isReflCo co then e else Cast e co++-- ---------------------------------------------------------------------------+-- Override: per-field deriving modifiers (see docs/override-design.md)+-- ---------------------------------------------------------------------------++-- | Peel @Override1 cfg f@ to the real constructor and its per-field positional+-- modifiers (single inner list); a non-overridden @f@ gives @(f, Nothing)@.+peelOverride1 :: GenEnv -> Type -> (Type, Maybe [Type])+peelOverride1 gen = peelOverride1With (ovTcsGen "Override1" gen)++-- | The @Override@-config 'TyCon's a config decoder needs. Bundled so the+-- satellite 'Deriver1'\/'Deriver2's (which have no 'GenEnv') can pass them.+data OvTcs = OvTcs+ { ovWrap :: Maybe TyCon -- ^ @Override1@ \/ @Override2@+ , ovKeep :: Maybe TyCon -- ^ @Keep@+ , ovArrow :: Maybe TyCon -- ^ @-->@+ , ovAssign :: Maybe TyCon -- ^ @:=@+ , ovAt :: Maybe TyCon -- ^ @At@+ }++-- | The bundle, from a 'GenEnv' (for the built-in synthesizers).+ovTcsGen :: String -> GenEnv -> OvTcs+ovTcsGen wrap gen = OvTcs+ (if wrap == "Override2" then geOverride2 gen else geOverride1 gen)+ (geKeep gen) (geArrow gen) (geAssign gen) (geAt gen)++-- | The bundle, looked up by name (for the satellite 'Deriver1'\/'Deriver2's).+lookupOvTcs :: String -> TcPluginM OvTcs+lookupOvTcs wrap = OvTcs+ <$> lookupTyConMaybe "Stock.Override" wrap+ <*> lookupTyConMaybe "Stock.Override" "Keep"+ <*> lookupTyConMaybe "Stock.Override" "-->"+ <*> lookupTyConMaybe "Stock.Override" ":="+ <*> lookupTyConMaybe "Stock.Override" "At"++-- | As 'peelOverride1', but taking the 'TyCon' bundle directly so callers+-- without a 'GenEnv' (the companion 'Deriver1's) can peel @Override1@ too.+peelOverride1With :: OvTcs -> Type -> (Type, Maybe [Type])+peelOverride1With tcs f = case ovWrap tcs of+ Just ov1Tc | Just (tc, [_, _, realF, cfg]) <- splitTyConApp_maybe f, tc == ov1Tc+ -> (realF, decodeOvCfg tcs realF cfg)+ _ -> (f, Nothing)++-- | Decode an @Override1@\/@Override2@ config to the (first) constructor's+-- per-field /raw/ modifiers (@Keep@ where a field is unaddressed). Both the+-- positional @'[ '[m, _, …] ]@ form AND the field-keyed entry list @'[ "x" ':=+-- m, 'C '--> 0 '--> m, … ]@ work — the same surface as value @Override@, only+-- the modifier kind differs (a functor here). 'modifierType' is /not/ applied:+-- the synthesizers receive @m@ and reshape @h a@ to @m a@ themselves.+decodeOvCfg :: OvTcs -> Type -> Type -> Maybe [Type]+decodeOvCfg tcs realInner cfg =+ case decodePositional cfg of+ Just perCon -> listToMaybe perCon -- positional [[..]] form+ Nothing -> do -- field-keyed entry list+ arrowTc <- ovArrow tcs ; assignTc <- ovAssign tcs+ atTc <- ovAt tcs ; keepTc <- ovKeep tcs+ fTc <- tyConAppTyCon_maybe realInner+ let dcons = tyConDataCons fTc+ guard (not (null dcons))+ entries <- promotedListElems cfg >>= traverse (decodeEntry arrowTc assignTc atTc)+ cells <- either (const Nothing) Just (resolveCellsRaw dcons realInner entries)+ -- @realInner@ is an unsaturated @j -> Type@ here, so use the source arity+ -- (not 'fieldTysAt', which would instantiate the datacon and panic).+ Just [ fromMaybe (mkTyConTy keepTc) (lookup (0, fi) cells)+ | fi <- [0 .. dataConSourceArity (head dcons) - 1] ]++-- | The modifier functor for field @i@ under an @Override1@ config, if any (and+-- not @Keep@): the field's @h a@ is then reshaped to @m a@.+override1Mod :: GenEnv -> Maybe [Type] -> Int -> Maybe Type+override1Mod gen = override1ModWith (geKeep gen)++-- | As 'override1Mod', but taking the @Keep@ 'TyCon' directly (for 'Deriver1's).+override1ModWith :: Maybe TyCon -> Maybe [Type] -> Int -> Maybe Type+override1ModWith mKeep mMods i = case mMods of+ Just mods | i < length mods+ , let m = mods !! i+ , not (maybe False (\k -> tyConAppTyCon_maybe m == Just k) mKeep)+ -> Just m+ _ -> Nothing++-- | @Stock1 (Override1 cfg realF) t ~R realF t@ — two newtype hops (one when+-- there is no @Override1@ wrapper).+coDown1 :: GenEnv -> TyCon -> Type -> Type -> Type -> Type -> Coercion+coDown1 gen = coDown1With (geOverride1 gen)++-- | As 'coDown1', but taking the @Override1@ 'TyCon' directly (for 'Deriver1's).+coDown1With :: Maybe TyCon -> TyCon -> Type -> Type -> Type -> Type -> Coercion+coDown1With mOv1 st1Tc wrappedTy f0 realF t = mkTransCo+ (mkUnbranchedAxInstCo Representational (newTyConCo st1Tc) (tyConAppArgs wrappedTy ++ [t]) [])+ (case mOv1 of+ Just ov1Tc | tyConAppTyCon_maybe f0 == Just ov1Tc ->+ mkUnbranchedAxInstCo Representational (newTyConCo ov1Tc) (tyConAppArgs f0 ++ [t]) []+ _ -> mkRepReflCo (mkAppTy realF t))++-- | The @Stock2@ counterpart of 'peelOverride1With': peel @Override2 cfg realP@+-- to the real constructor and its per-field positional modifiers (for 'Deriver2's).+peelOverride2With :: OvTcs -> Type -> (Type, Maybe [Type])+peelOverride2With tcs p = case ovWrap tcs of+ Just ov2Tc | Just (tc, [_, rp, cfg]) <- splitTyConApp_maybe p, tc == ov2Tc+ -> (rp, decodeOvCfg tcs rp cfg)+ _ -> (p, Nothing)++-- | @Stock2 (Override2 cfg realP) t1 t2 ~R realP t1 t2@ — two newtype hops (one+-- when there is no @Override2@ wrapper). For 'Deriver2's.+coDown2With :: Maybe TyCon -> TyCon -> Type -> Type -> Type -> Type -> Type -> Coercion+coDown2With mOv2 st2Tc wrappedTy p0 realP t1 t2 = mkTransCo+ (mkUnbranchedAxInstCo Representational (newTyConCo st2Tc) (tyConAppArgs wrappedTy ++ [t1, t2]) [])+ (case mOv2 of+ Just ov2Tc | tyConAppTyCon_maybe p0 == Just ov2Tc ->+ mkUnbranchedAxInstCo Representational (newTyConCo ov2Tc) (tyConAppArgs p0 ++ [t1, t2]) []+ _ -> mkRepReflCo (mkAppTy (mkAppTy realP t1) t2))++-- | Recognise @Stock (Override T cfg)@ and build the override representation of+-- @T@. The unwrap coercion chains through /both/ newtypes; fields named in+-- @cfg@ take their modifier type, with a per-cell @realτ ~R modτ@ coercion+-- emitted as a wanted (so GHC validates the override and reports a clean error+-- if it isn't coercible); unnamed fields are unchanged. 'Nothing' if this is+-- not an @Override@; @Left@ if it is but malformed. v1: single-constructor,+-- keyed by record-field name, modifiers saturated (@Type@, pin) or unary+-- (@Type -> Type@, broadcast).+-- | Representational primitive equality @a ~R# b@ — the wanted whose evidence+-- coercion we splice per overridden cell. (Renamed in GHC 9.14.)+mkStockReprEq :: Type -> Type -> Type+#if MIN_VERSION_ghc(9,14,0)+mkStockReprEq = mkReprEqPred+#else+mkStockReprEq = mkReprPrimEqPred+#endif++-- | Pure decode of @Stock (Override T cfg)@ to @T@ and its constructors paired+-- with their per-field /modifier/ types (@Keep@ or an unmatched cell ⇒ the real+-- field type). The 'Generic' Rep rewriter ('Stock.Generic.rewriteRep') needs+-- only these types; the value-level coercion wanteds are emitted by the solver+-- ('synthGeneric' via 'mkOverrideRepr'), and both compute identical modifier+-- types (same 'modifierType') so the @Rep@ and @from@\/@to@ cohere. 'Nothing'+-- if @arg@ is not a @Stock (Override …)@ (the caller falls back to 'mkRepr').+overrideFieldTypes :: GenEnv -> Type -> Maybe (Type, [(DataCon, [Type])])+overrideFieldTypes gen arg = do+ ourStock <- geStock gen+ overTc <- geOverride gen+ keepTc <- geKeep gen ; arrowTc <- geArrow gen+ assignTc <- geAssign gen ; atTc <- geAt gen+ (stockTc, [innerOver]) <- splitTyConApp_maybe arg+ guard (stockTc == ourStock)+ (oTc, oArgs) <- splitTyConApp_maybe innerOver+ guard (oTc == overTc)+ (cfg : realInner : _) <- pure (reverse oArgs)+ innerTc <- tyConAppTyCon_maybe realInner+ let dcons = tyConDataCons innerTc+ guard (not (null dcons))+ perCon <-+ case decodePositional cfg of+ Just perCon -- positional [[..]] form+ | length perCon == length dcons ->+ sequence (zipWith (posCon keepTc realInner) dcons perCon)+ | otherwise -> Nothing+ Nothing -> do -- entry-list / --> path form+ entries <- promotedListElems cfg >>= traverse (decodeEntry arrowTc assignTc atTc)+ case resolveCells dcons realInner entries of+ Left _ -> Nothing+ Right cells -> Just [ [ fromMaybe rft (lookup (ci, fi) cells)+ | (fi, rft) <- zip [0 ..] (fieldTysAt realInner dc) ]+ | (ci, dc) <- zip [0 :: Int ..] dcons ]+ pure (realInner, zip dcons perCon)+ where+ -- one positional constructor: each slot a modifier type or @Keep@ (no change)+ posCon keepTc realInner dc mods+ | length mods /= length rfts = Nothing+ | otherwise = sequence (zipWith cell rfts mods)+ where rfts = fieldTysAt realInner dc+ cell rft m+ | tyConAppTyCon_maybe m == Just keepTc = Just rft+ | otherwise = either (const Nothing) Just (modifierType m rft)++mkOverrideRepr :: GenEnv -> CtLoc -> Type -> TcPluginM (Maybe (Either SDoc (Repr, [Ct])))+mkOverrideRepr gen loc wrappedTy+ | Just ourStock <- geStock gen+ , Just overTc <- geOverride gen+ , Just assignTc <- geAssign gen+ , Just (stockTc, [innerOver]) <- splitTyConApp_maybe wrappedTy+ , stockTc == ourStock+ , Just (oTc, oArgs) <- splitTyConApp_maybe innerOver+ , oTc == overTc+ , (cfg : realInner : _) <- reverse oArgs -- last two visible args (drop the invisible cfg kind)+ , Just atTc <- geAt gen+ , Just keepTc <- geKeep gen+ , Just arrowTc <- geArrow gen+ = Just <$> buildOverride loc ourStock overTc assignTc atTc keepTc arrowTc innerOver cfg realInner+ | otherwise = pure Nothing++-- | The body of 'mkOverrideRepr', once it is known to be an @Override@.+-- Two config shapes (see @docs\/override-design.md@): a /positional/+-- list-of-lists @'[ '[m, …], … ]@ (one inner list per constructor, one element+-- per field, @Keep@ = no change), or an /entry list/ @'[ sel ':= m, 'C --> n+-- --> m, … ]@ — both multi-constructor, selector- and path-addressed.+buildOverride :: CtLoc -> TyCon -> TyCon -> TyCon -> TyCon -> TyCon -> TyCon+ -> Type -> Type -> Type -> TcPluginM (Either SDoc (Repr, [Ct]))+buildOverride loc ourStock overTc assignTc atTc keepTc arrowTc innerOver cfg realInner =+ case tyConAppTyCon_maybe realInner of+ Nothing -> bad (text "Override target is not a concrete algebraic type:" <+> ppr realInner)+ Just innerTc -> case tyConDataCons innerTc of+ [] -> bad (text "Override: type has no constructors:" <+> ppr realInner)+ dcons+ | any dcUnpacked dcons -> bad (text "Override: a constructor has UNPACKed/strict-unboxed"+ <+> text "or existential fields, unsupported")+ -- positional [[..]] form: one inner list per constructor+ | Just perCon <- decodePositional cfg ->+ buildPositional loc ourStock overTc keepTc innerOver cfg realInner dcons perCon+ -- entry-list form ( := / At / --> paths ), multi-constructor+ | otherwise ->+ case promotedListElems cfg >>= traverse (decodeEntry arrowTc assignTc atTc) of+ Nothing -> bad (text "Override config is not a concrete list of"+ <+> text "selector := modifier / path --> modifier entries:" <+> ppr cfg)+ Just entries -> resolveOverride loc ourStock overTc innerOver cfg realInner dcons entries+ where bad = pure . Left++-- | A positional config @'[ '[m00, m01, …], … ]@ as per-constructor,+-- per-field modifier lists, or 'Nothing' if @cfg@ is not a concrete+-- list-of-lists (in which case the entry-list decoder is tried instead).+decodePositional :: Type -> Maybe [[Type]]+decodePositional cfg = case promotedListElems cfg of+ Just es@(_ : _) -> traverse promotedListElems es -- one inner list per constructor+ _ -> Nothing -- empty @'[]@ is identity, not "0+ -- constructors": fall through to the+ -- entry-list branch (@resolveOverride []@)++-- | Build the 'Repr' for a positional config: each constructor's inner list+-- gives a modifier per field — @Keep@ leaves the field, any other type @m@+-- replaces it (kind-dispatched 'pin' vs 'broadcast' by 'modifierType'), with a+-- per-cell @realτ ~R modτ@ coercion emitted as a wanted.+buildPositional :: CtLoc -> TyCon -> TyCon -> TyCon -> Type -> Type -> Type+ -> [DataCon] -> [[Type]] -> TcPluginM (Either SDoc (Repr, [Ct]))+buildPositional loc ourStock overTc keepTc innerOver cfg realInner dcons perCon+ | length perCon /= length dcons =+ pure (Left (text "Override: positional config has" <+> int (length perCon)+ <+> text "constructor list(s) but" <+> ppr realInner <+> text "has"+ <+> int (length dcons)))+ | otherwise = do+ let co = mkTransCo (mkUnbranchedAxInstCo Representational (newTyConCo ourStock) [innerOver] [])+ (mkUnbranchedAxInstCo Representational (newTyConCo overTc) [typeKind cfg, realInner, cfg] [])+ results <- traverse (uncurry conInfo) (zip dcons perCon)+ pure $ case sequence results of+ Left err -> Left err+ Right cws -> Right (Repr realInner co (map fst cws), concatMap snd cws)+ where+ conInfo :: DataCon -> [Type] -> TcPluginM (Either SDoc (ConInfo, [Ct]))+ conInfo dc mods+ | length mods /= length realFts =+ pure (Left (text "Override: constructor" <+> ppr dc <+> text "has" <+> int (length realFts)+ <+> text "field(s) but its positional list has" <+> int (length mods)))+ | otherwise = do+ cells <- traverse cell (zip realFts mods)+ pure $ case sequence cells of+ Left err -> Left err+ Right fs -> Right (ConInfo dc (map (fst . fst) fs) (map (snd . fst) fs)+ , concatMap snd fs)+ where realFts = fieldTysAt realInner dc+ -- one field: Keep ⇒ (realτ, Refl); modifier m ⇒ (modτ, evidence coercion + wanted)+ cell :: (Type, Type) -> TcPluginM (Either SDoc ((Type, Coercion), [Ct]))+ cell (ft, m)+ | tyConAppTyCon_maybe m == Just keepTc = pure (Right ((ft, mkRepReflCo ft), []))+ | otherwise = case modifierType m ft of+ Left err -> pure (Left err)+ Right modTy -> do+ ev <- newWanted loc (mkStockReprEq ft modTy)+ pure (Right ((modTy, ctEvCoercion ev), [mkNonCanonical ev]))++-- | A path hop (design §4): a constructor, a field by position, or a field by+-- label. Constructor hops match by /occ-name/, so both @'P@ and (for a+-- single-constructor type) the bare type name resolve.+data Hop = HopCon FastString | HopPos Integer | HopLabel FastString++-- | A decoded entry's address: a @-->@ \/ @:=@ path of hops (narrowing the+-- @(constructor, field)@ scope), or a type selector.+data Addr = AddrPath [Hop] | AddrType Type++-- | Resolve decoded @(addr, modifier)@ entries against /all/ the type's+-- constructors: turn each address into its cell set @(ctorIndex, fieldIndex)@,+-- reject any cell claimed twice, kind-dispatch each modifier per cell, emit the+-- per-cell coercion wanteds, and assemble the (multi-constructor) 'Repr'.+resolveOverride :: CtLoc -> TyCon -> TyCon -> Type -> Type -> Type -> [DataCon]+ -> [(Addr, Type)] -> TcPluginM (Either SDoc (Repr, [Ct]))+resolveOverride loc ourStock overTc innerOver cfg realInner dcons entries =+ case resolveCells dcons realInner entries of+ Left err -> pure (Left err)+ Right cells -> do+ tagged <- for cells \((ci, fi), modTy) -> do+ let realFt = fieldTysAt realInner (dcons !! ci) !! fi+ ev <- newWanted loc (mkStockReprEq realFt modTy)+ pure (((ci, fi), (modTy, ctEvCoercion ev)), mkNonCanonical ev)+ let cellMap = map fst tagged+ wanteds = map snd tagged+ -- Stock (Override T cfg) ~R Override T cfg ~R T+ co = mkTransCo (mkUnbranchedAxInstCo Representational (newTyConCo ourStock) [innerOver] [])+ (mkUnbranchedAxInstCo Representational (newTyConCo overTc) [typeKind cfg, realInner, cfg] [])+ cons = [ ConInfo dc (map fst fields) (map snd fields)+ | (ci, dc) <- zip [0 :: Int ..] dcons+ , let fields = [ fromMaybe (ft, mkRepReflCo ft) (lookup (ci, fi) cellMap)+ | (fi, ft) <- zip [0 :: Int ..] (fieldTysAt realInner dc) ] ]+ pure (Right (Repr realInner co cons, wanteds))++-- | As 'resolveCells', but keeping the /raw/ modifier @m@ per cell (not+-- 'modifierType'-applied) — for @Override1@\/@Override2@, whose synthesizers+-- want the bare functor modifier (they reshape @h a@ to @m a@ themselves).+resolveCellsRaw :: [DataCon] -> Type -> [(Addr, Type)] -> Either SDoc [((Int, Int), Type)]+resolveCellsRaw dcons targetTy = go []+ where+ go _ [] = Right []+ go claimed ((addr, m) : rest) = do+ cells <- resolveAddr dcons targetTy addr+ case filter (`elem` claimed) cells of+ clash@(_ : _) -> Left (text "Override: cell(s)" <+> ppr clash+ <+> text "claimed by more than one entry (make them disjoint)")+ [] -> ((map (\c -> (c, m)) cells) ++) <$> go (cells ++ claimed) rest++-- | Resolve every entry to its cells (with kind-dispatched modifier types),+-- left to right, enforcing the no-overlap law against the cells already claimed.+resolveCells :: [DataCon] -> Type -> [(Addr, Type)]+ -> Either SDoc [((Int, Int), Type)]+resolveCells dcons targetTy = go []+ where+ go _ [] = Right []+ go claimed ((addr, m) : rest) = do+ cells <- resolveAddr dcons targetTy addr+ case filter (`elem` claimed) cells of+ clash@(_ : _) -> Left (text "Override: cell(s)" <+> ppr clash+ <+> text "claimed by more than one entry (make them disjoint)")+ [] -> do+ here <- for cells \(ci, fi) ->+ (,) (ci, fi) <$> modifierType m (fieldTysAt targetTy (dcons !! ci) !! fi)+ (here ++) <$> go (cells ++ claimed) rest++-- | Resolve one address to its @(ctorIndex, fieldIndex)@ cell set.+resolveAddr :: [DataCon] -> Type -> Addr -> Either SDoc [(Int, Int)]+resolveAddr dcons targetTy addr = case addr of+ AddrType t+ | t `eqType` targetTy -> Right (allCells dcons targetTy)+ | otherwise -> case [ (ci, fi) | (ci, dc) <- zip [0 ..] dcons+ , (fi, ft) <- zip [0 ..] (fieldTysAt targetTy dc)+ , ft `eqType` t ] of+ [] -> Left (text "Override: no field of type" <+> quotes (ppr t))+ cs -> Right cs+ AddrPath hops -> foldHops dcons targetTy (allCells dcons targetTy) hops++-- | Narrow the cell scope by each hop in turn.+foldHops :: [DataCon] -> Type -> [(Int, Int)] -> [Hop] -> Either SDoc [(Int, Int)]+foldHops _ _ scope [] = Right scope+foldHops dcons targetTy scope (HopCon nm : hs) =+ case [ ci | (ci, dc) <- zip [0 ..] dcons, occNameFS (getOccName dc) == nm ] of+ [] -> Left (text "Override: unknown constructor" <+> quotes (ftext nm))+ cis -> foldHops dcons targetTy (filter ((`elem` cis) . fst) scope) hs+foldHops dcons targetTy scope (HopPos n : hs) =+ case filter ((== fromInteger n) . snd) scope of+ [] -> Left (text "Override: no field at position" <+> integer n <+> text "in the addressed scope")+ sc' -> foldHops dcons targetTy sc' hs+foldHops dcons targetTy scope (HopLabel l : hs) =+ case [ (ci, fi) | (ci, fi) <- scope, labelAt (dcons !! ci) fi == Just (unpackFS l) ] of+ [] -> Left (text "Override: no field labelled" <+> quotes (ftext l) <+> text "in the addressed scope")+ sc' -> foldHops dcons targetTy sc' hs++-- | Every @(ctorIndex, fieldIndex)@ cell of the type. Uses the source arity+-- (not 'fieldTysAt') so it is safe when @targetTy@ is an unsaturated @j -> Type@+-- (the @Override1@\/@Override2@ case).+allCells :: [DataCon] -> Type -> [(Int, Int)]+allCells dcons _ =+ [ (ci, fi) | (ci, dc) <- zip [0 ..] dcons, fi <- [0 .. dataConSourceArity dc - 1] ]++-- | The record label of a constructor's @i@-th field, if it has one.+labelAt :: DataCon -> Int -> Maybe String+labelAt dc i =+ let ls = map (occNameString . nameOccName . flSelector) (dataConFieldLabels dc)+ in if i < length ls then Just (ls !! i) else Nothing++-- | Kind-dispatch a modifier: a saturated @Type@ pins the field to that type;+-- a unary @Type -> Type@ is applied to the field's own type (broadcast).+modifierType :: Type -> Type -> Either SDoc Type+modifierType m fieldTy+ | k `eqType` liftedTypeKind = Right m+ | k `eqType` mkVisFunTyMany liftedTypeKind liftedTypeKind = Right (mkAppTy m fieldTy)+ | otherwise = Left (text "Override: modifier" <+> ppr m <+> text "has unsupported kind"+ <+> ppr k <+> text "(expected Type or Type -> Type)")+ where k = typeKind m++-- | A balanced list of the elements of a promoted type-level list+-- (@'[a, b, …]@), or 'Nothing' if @ty@ is not a concrete promoted list.+promotedListElems :: Type -> Maybe [Type]+promotedListElems ty = do+ (tc, args) <- splitTyConApp_maybe ty+ if | tc == promotedNilDataCon -> Just []+ | tc == promotedConsDataCon -> case args of+ [_k, x, rest] -> (x :) <$> promotedListElems rest+ _ -> Nothing+ | otherwise -> Nothing++-- | Decode one config entry into its address and modifier. Three surfaces:+-- a @-->@ path (@'P --> 0 --> m@), a @:=@ entry (@"x" := m@ or @At C n := m@),+-- or — still through @:=@ — a type selector (@Int := m@). Robust to leading+-- invisible kind arguments (the visible operands are the last two).+decodeEntry :: TyCon -> TyCon -> TyCon -> Type -> Maybe (Addr, Type)+decodeEntry arrowTc assignTc atTc e+ | Just (hops, m) <- decodeArrow arrowTc e =+ (\hs -> (AddrPath hs, m)) <$> traverse decodeHop hops+ | Just (tc, args) <- splitTyConApp_maybe e, tc == assignTc+ , (m : sel : _) <- reverse args = (, m) <$> decodeSel atTc sel+ | otherwise = Nothing++-- | Flatten a right-nested @a --> b --> … --> m@ into its hop types and the+-- terminal modifier; 'Nothing' if @e@ is not a @-->@ application.+decodeArrow :: TyCon -> Type -> Maybe ([Type], Type)+decodeArrow arrowTc e = do+ (tc, args) <- splitTyConApp_maybe e+ guard (tc == arrowTc)+ case reverse args of+ (rhs : lhs : _) -> case decodeArrow arrowTc rhs of+ Just (hs, m) -> Just (lhs : hs, m) -- rhs continues the path+ Nothing -> Just ([lhs], rhs) -- rhs is the terminal modifier+ _ -> Nothing++-- | Classify a path hop by kind: 'Symbol' ⇒ label, 'Nat' ⇒ position, otherwise+-- a (promoted constructor \/ type) matched later by occ-name.+decodeHop :: Type -> Maybe Hop+decodeHop h+ | Just fs <- isStrLitTy h = Just (HopLabel fs)+ | Just n <- isNumLitTy h = Just (HopPos n)+ | Just tc <- tyConAppTyCon_maybe h = Just (HopCon (occNameFS (getOccName tc)))+ | otherwise = Nothing++-- | Classify the left of @:=@: a 'Symbol' is a label path, @At C n@ a+-- constructor+position path, anything else a type selector.+decodeSel :: TyCon -> Type -> Maybe Addr+decodeSel atTc sel+ | Just fs <- isStrLitTy sel = Just (AddrPath [HopLabel fs])+ | Just (tc, args) <- splitTyConApp_maybe sel, tc == atTc+ , (pos : con : _) <- reverse args, Just n <- isNumLitTy pos+ , Just ctc <- tyConAppTyCon_maybe con =+ Just (AddrPath [HopCon (occNameFS (getOccName ctc)), HopPos n])+ | otherwise = Just (AddrType sel)++-- | Does any cell carry a non-trivial override (a modifier coercion that isn't+-- reflexivity)? The raw @viaSynth@ synthesizers (Ord\/Show\/Read\/Enum\/Ix)+-- recompute field types from the constructor and so cannot honour an override;+-- the dispatcher uses this to reject them loudly rather than silently ignore it.+reprOverridden :: Repr -> Bool+reprOverridden = any (any (not . isReflCo) . ciFieldCos) . rCons++-- Representation predicates ("checking the datatype representation").+reprHasFields, reprIsEnum, reprSingleCon, reprEmpty :: Repr -> Bool+reprHasFields = any (not . null . ciFields) . rCons+-- GHC: an enumeration has >= 1 nullary constructor. Requiring non-empty here+-- makes Enum/Ix/Bounded reject 0-constructor types cleanly (rather than build+-- degenerate Core: maxTag = -1, head/last of []), matching GHC's rejection.+reprIsEnum r = not (reprEmpty r) && not (reprHasFields r)+reprSingleCon = (== 1) . length . rCons+reprEmpty = null . rCons++-- | Does any constructor have fields whose runtime representation differs from+-- their source types? This happens with @UNPACK@ / @-funbox-small-strict-fields@+-- (a strict @!Int@ becomes @Int#@) and with existentials/GADTs. We match on the+-- source types, so such constructors would yield ill-typed Core — we refuse them.+reprUnpacked :: Repr -> Bool+reprUnpacked = any (dcUnpacked . ciCon) . rCons++-- | True if a constructor's runtime arg representation differs from its source+-- arg types (UNPACK, @-funbox-small-strict-fields@, existentials, …).+dcUnpacked :: DataCon -> Bool+dcUnpacked dc =+ let rep = map scaledThing (dataConRepArgTys dc)+ orig = map scaledThing (dataConOrigArgTys dc)+ in length rep /= length orig || not (and (zipWith eqType rep orig))++-- | Apply a class's dictionary constructor: @C:Cls \@ty m1 .. mn@.+mkClassDict :: Class -> Type -> [CoreExpr] -> CoreExpr+mkClassDict cls ty methods =+ mkApps (Var (dataConWorkId (classDataCon cls))) (Type ty : methods)++-- | A constructor's field types, instantiated at the inner type's arguments,+-- so a parameterised type such as @Pair Int@ yields @[Int, Int]@ rather than+-- @[a, a]@ (and @Pair a@ yields the skolem @[a, a]@).+fieldTysAt :: Type -> DataCon -> [Type]+fieldTysAt innerTy dc = map scaledThing (dataConInstOrigArgTys dc (tyConAppArgs innerTy))++-- | Apply a constructor, supplying the inner type's type arguments first+-- (e.g. @Pair \@Int e1 e2@), so it works for parameterised types.+conAppAt :: Type -> DataCon -> [CoreExpr] -> CoreExpr+conAppAt innerTy dc args = mkCoreConApps dc (map Type (tyConAppArgs innerTy) ++ args)++-- | Build a (possibly self-referential) dictionary: @let rec d = C:Cls ty (mk d)+-- in d@. The callback receives the dictionary binder so fields can refer back+-- to it (e.g. to use class default methods).+recClassDict :: Class -> Type -> (Id -> TcPluginM [CoreExpr]) -> TcPluginM CoreExpr+recClassDict cls ty mk = do+ dvar <- freshId (mkClassPred cls [ty]) "dict"+ fields <- mk dvar+ pure (Let (Rec [(dvar, mkClassDict cls ty fields)]) (Var dvar))++-- | Build a recursive dictionary giving explicit superclass dicts and explicit+-- implementations for the listed method indices; every other method comes from+-- the class's own default method (applied to the recursive dictionary). This+-- is how we fill many-method classes (@Foldable@) from a single key method.+recDictWith :: Class -> Type -> [CoreExpr] -> [(Int, CoreExpr)] -> TcPluginM CoreExpr+recDictWith cls ty supers overrides = do+ dvar <- freshId (mkClassPred cls [ty]) "dict"+ methodFields <- for (zip [0 ..] (classMethods cls)) \(i, _) ->+ case lookup i overrides of+ Just e -> pure e+ Nothing -> do dm <- defMethId cls i+ pure (mkApps (Var dm) [Type ty, Var dvar])+ pure (Let (Rec [(dvar, mkClassDict cls ty (supers ++ methodFields))]) (Var dvar))++-- | How a constructor field relates to the functor parameter @a@.+data FieldKind = FParam | FConst | FApp Type -- ^ is @a@ / no @a@ / @H a@ (covariant)++classifyField :: TyVar -> Type -> Type -> Maybe FieldKind+classifyField atv aTy ft+ | ft `eqType` aTy = Just FParam+ | not (atv `elemVarSet` tyCoVarsOfType ft) = Just FConst+ | Just (h, larg) <- splitAppTy_maybe ft+ , larg `eqType` aTy+ , not (atv `elemVarSet` tyCoVarsOfType h) = Just (FApp h)+ | otherwise = Nothing++-- | How to use one constructor field, by its relationship to the parameter.+-- This is the single place that distinguishes a lifted class (@Eq1@\/@Ord1@\/+-- @Show1@\/@Read1@) from its twin: the @onParam@ leaf is what changes (the+-- supplied function vs the field's own instance). @onConst@\/@onApply@ receive+-- the wanted-evidence the field needs.+data Roles r = Roles+ { onParam :: r -- ^ the field /is/ the parameter @a@+ , onConst :: CtEvidence -> Type -> r -- ^ a constant field (own instance)+ -- | an @H a@ field (lifted instance of the /effective/ @H@): the evidence,+ -- the effective functor (the @Override1@ modifier @m@ when present, else the+ -- real @h@), and a coercion builder @\\t -> (h t ~R m t)@ ('Refl' when not+ -- overridden) so the caller can cast field values @h t@ to\/from @m t@.+ , onApply :: CtEvidence -> Type -> (Type -> Coercion) -> r+ }++-- | Classify a field and pick the matching role, emitting the wanted that role+-- needs (a @C H@ for a constant, the lifted @C1 H@ for an @H a@ field). Under an+-- @Override1@ the @H a@ field is reshaped to @m a@: the wanted is the lifted @C1+-- m@ and 'onApply' receives @m@ plus the @h t ~R m t@ coercion builder.+-- 'Nothing' if the field shape is unsupported (e.g. contravariant, nested).+interpField :: Class -- ^ the constant-field class (@Eq@\/@Ord@\/@Show@\/@Read@)+ -> Class -- ^ the lifted class (@Eq1@\/@Ord1@\/@Show1@\/@Read1@)+ -> TyVar -> Type -> CtLoc+ -> Maybe Type -- ^ @Override1@ modifier for this field, if any+ -> Roles r -> Type -> TcPluginM (Maybe (r, [Ct]))+interpField constCls liftCls atv aTy loc mMod roles ftA =+ case classifyField atv aTy ftA of+ Nothing -> pure Nothing+ Just FParam -> pure (Just (onParam roles, []))+ Just FConst -> do+ ev <- newWanted loc (mkClassPred constCls [ftA])+ pure (Just (onConst roles ev ftA, [mkNonCanonical ev]))+ Just (FApp h) -> do+ let m = fromMaybe h mMod+ coB t = case mMod of+ Nothing -> mkRepReflCo (mkAppTy h t)+ Just _ -> mkStockCo (PluginProv "stock") Representational+ (mkAppTy h t) (mkAppTy m t)+ ev <- newWanted loc (mkClassPred liftCls [m])+ pure (Just (onApply roles ev m coB, [mkNonCanonical ev]))++-- | The field types of a constructor with the @Stock1@ parameter set to @ty@.+fieldsAt :: [Type] -> DataCon -> Type -> [Type]+fieldsAt fixed dc ty = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [ty]))++-- | The two-scrutinee SOP walk — the @Stock1@ counterpart to 'matchSOP'+-- (which is single-scrutinee, in "Stock.Derive"). Walk two values of the same+-- @Stock1 F@ shape in lock-step: matching constructors combine their per-field+-- results, mismatched constructors give a fixed answer. This is the skeleton+-- shared by @liftEq@ (combine = short-circuit @&&@, mismatch = @False@) and+-- @liftCompare@ (combine = lexicographic, mismatch = tag order). @fieldOp@+-- produces one field-pair's result (via 'interpField'); @combine@ folds a+-- constructor's field results.+zipLift2 :: TyCon -> [Type] -> (Type -> Coercion)+ -> Type -> Type -> Type -- a, b, result type+ -> Id -> Id -- the two scrutinees (fa, fb)+ -> (Int -> Int -> CoreExpr) -- mismatched-constructor result+ -> ([CoreExpr] -> TcPluginM CoreExpr) -- combine field results+ -> (Int -> Type -> Id -> Id -> TcPluginM (Maybe (CoreExpr, [Ct]))) -- per field pair (with index)+ -> TcPluginM (Maybe (CoreExpr, [Ct]))+zipLift2 fTc fixed coAt aTy bTy resTy faId fbId mismatch combine fieldOp = do+ let dcons = tyConDataCons fTc+ innerA = mkTyConApp fTc (fixed ++ [aTy])+ innerB = mkTyConApp fTc (fixed ++ [bTy])+ indexed = zip [0 :: Int ..] dcons+ freshFields dc ty = zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..]+ (fieldsAt fixed dc ty)+ mInner <- for indexed \(i, dci) -> do+ xs <- freshFields dci aTy+ mAlts <- for indexed \(j, dcj) -> do+ ys <- freshFields dcj bTy+ if i /= j+ then pure (Just (Alt (DataAlt dcj) ys (mismatch i j), []))+ else do+ mops <- sequence (zipWith4 fieldOp [0 :: Int ..] (fieldsAt fixed dci aTy) xs ys)+ case sequence mops of+ Nothing -> pure Nothing+ Just ows -> do+ body <- combine (map fst ows)+ pure (Just (Alt (DataAlt dcj) ys body, concatMap snd ows))+ case sequence mAlts of+ Nothing -> pure Nothing+ Just altWss -> do+ let (alts, wss) = unzip altWss+ cbB <- freshId innerB "cbb"+ pure (Just ( Alt (DataAlt dci) xs+ (destructInner fTc (fixed ++ [bTy]) (Cast (Var fbId) (coAt bTy)) cbB resTy alts)+ , concat wss ))+ case sequence mInner of+ Nothing -> pure Nothing+ Just altWss -> do+ let (alts, wss) = unzip altWss+ cbA <- freshId innerA "cba"+ pure (Just ( destructInner fTc (fixed ++ [aTy]) (Cast (Var faId) (coAt aTy)) cbA resTy alts+ , concat wss ))++-- | Solve @C (Stock Inner)@ by building the dictionary from @Inner@'s+-- constructors. We only act on the @Stock@ newtype, so unrelated code is+-- never affected. @Eq@ handles any single-level algebraic type; @Ord@ is+-- limited to enumerations; anything else gets a clear "not implemented" error.+-- | A solver for one wrapper: 'Just' the 'Attempt' if it owns the wrapper (even+-- an error it reports), or 'Nothing' to defer to the next. The Monoid is+-- first-success, so dispatch is a composition @stockSolver \<\> …@ — and a+-- companion solver would be just one more element.+-- The first-success Monoid is exactly @Alt (MaybeT m)@ (the Alternative-as-+-- Monoid that stops at the first solver returning a result), under the reader+-- arrows — so we derive it rather than hand-write it.+newtype Solver = Solver+ { runSolver :: PluginState -> Ct -> Class -> Type -> TcPluginM (Maybe Attempt) }+ deriving (Semigroup, Monoid)+ via (PluginState -> Ct -> Class -> Type -> Mon.Alt (MaybeT TcPluginM) Attempt)++notImplemented :: PluginState -> Ct -> SDoc -> TcPluginM Attempt+notImplemented st ct doc = do+ let key = showSDocUnsafe doc+ seen <- tcPluginIO (readIORef (psSeen st))+ unless (key `elem` seen) $ do+ tcPluginIO (modifyIORef' (psSeen st) (key :))+ unsafeTcPluginTcM (addErrTc (mkTcRnUnknownMessage (mkPlainError noHints doc)))+ pure (Nothing, [], [ct])++-- | A fresh local binder of the given type.+freshId :: Type -> String -> TcPluginM Id+freshId ty s = do+ u <- unsafeTcPluginTcM getUniqueM+ pure (mkLocalId (mkSystemName u (mkVarOcc s)) manyDataConTy ty)++-- | Build @compare :: wrapped -> wrapped -> Ordering@ for any single-level+-- algebraic type, matching derived @Ord@: compare constructor tags first, and+-- for the same constructor compare the fields lexicographically. Field+-- comparisons use each field type's own @Ord@ (requested as wanted+-- constraints); the wanteds are returned alongside the expression.+toDatatype :: Type -> Repr -> Datatype+toDatatype via repr = Datatype+ { dtVia = via+ , dtUnwrap = rCo repr+ , dtType = rInner repr+ , dtCons = [ Constructor dc (ciFields ci) defaultFixity labels (ciFieldCos ci)+ | ci <- rCons repr+ , let dc = ciCon ci+ fls = dataConFieldLabels dc+ labels = if null fls then Nothing else Just fls ]+ }++-- | Run a @Deriver@ (built-in or discovered) as a solve attempt.+runDeriverAttempt :: Deriver -> Ct -> Class -> Datatype -> TcPluginM Attempt+runDeriverAttempt drv ct cls dt = do+ (ev, ws) <- runSynth (ctLoc ct) (runDeriver drv cls dt)+ pure (Just (ev, ct), ws, [])++-- | Discovery + dynamic loading (the extension mechanism): if a companion+-- package provides @instance DeriveStock C@, find it in the instance+-- environment, load its @Deriver@ value with GHC's plugin loader, and run it —+-- so a new class becomes derivable @via Stock@ just by depending on the+-- companion, with no change to the user's @-fplugin@ line.+tryWitness :: PluginState -> Ct -> Class -> Datatype -> TcPluginM (Maybe Attempt)+tryWitness st ct cls dt =+ case geWitness (psGen st) of+ Nothing -> pure Nothing+ Just witCls -> do+ instEnvs <- getInstEnvs+ let clsTy = mkTyConTy (classTyCon cls)+ matches = [ inst | inst <- classInstances instEnvs witCls+ , [headTy] <- [is_tys inst], headTy `eqType` clsTy ]+ case matches of+ [] -> pure Nothing+ (inst : _) -> do+ let dfun = is_dfun inst+ hsc <- getTopEnv+ -- @DeriveStock@ is single-method with no superclass, so its dictionary+ -- is represented exactly as a @Deriver@; load the dfun at its own type+ -- and treat it as one.+ r <- unsafeTcPluginTcM $ liftIO $+ getValueSafely hsc (idName dfun) (idType dfun)+ case r of+ Right (drv, _, _) -> Just <$> runDeriverAttempt drv ct cls dt+ Left _ -> pure Nothing++-- | The @Stock1@ counterpart of 'tryWitness': discover a companion+-- @instance DeriveStock1 C@, load its 'Deriver1', and run it on the inner+-- type constructor @f@. (@deriving C via Stock1 F@ for a lifted @C@.)+tryWitness1 :: PluginState -> Ct -> Class -> Type -> Type -> TcPluginM (Maybe Attempt)+tryWitness1 st ct cls wrappedTy f =+ case geWitness1 (psGen st) of+ Nothing -> pure Nothing+ Just witCls -> do+ instEnvs <- getInstEnvs+ let clsTy = mkTyConTy (classTyCon cls)+ matches = [ inst | inst <- classInstances instEnvs witCls+ , [headTy] <- [is_tys inst], headTy `eqType` clsTy ]+ case matches of+ [] -> pure Nothing+ (inst : _) -> do+ let dfun = is_dfun inst+ hsc <- getTopEnv+ r <- unsafeTcPluginTcM $ liftIO $+ getValueSafely hsc (idName dfun) (idType dfun)+ case r of+ Right (Deriver1 synth, _, _) -> do+ m <- synth cls (ctLoc ct) wrappedTy f+ pure $ case m of+ Just (ev, ws) -> Just (Just (ev, ct), ws, [])+ Nothing -> Nothing+ Left _ -> pure Nothing++-- | The @Stock2@ counterpart of 'tryWitness1': discover @instance DeriveStock2+-- C@ and run its 'Deriver2' on the inner two-parameter constructor @p@.+tryWitness2 :: PluginState -> Ct -> Class -> Type -> Type -> TcPluginM (Maybe Attempt)+tryWitness2 st ct cls wrappedTy p =+ case geWitness2 (psGen st) of+ Nothing -> pure Nothing+ Just witCls -> do+ instEnvs <- getInstEnvs+ let clsTy = mkTyConTy (classTyCon cls)+ matches = [ inst | inst <- classInstances instEnvs witCls+ , [headTy] <- [is_tys inst], headTy `eqType` clsTy ]+ case matches of+ [] -> pure Nothing+ (inst : _) -> do+ let dfun = is_dfun inst+ hsc <- getTopEnv+ r <- unsafeTcPluginTcM $ liftIO $+ getValueSafely hsc (idName dfun) (idType dfun)+ case r of+ Right (Deriver2 synth, _, _) -> do+ m <- synth cls (ctLoc ct) wrappedTy p+ pure $ case m of+ Just (ev, ws) -> Just (Just (ev, ct), ws, [])+ Nothing -> Nothing+ Left _ -> pure Nothing++-- | @Eq@, re-expressed through the public SDK (@Datatype@ \/ @Synth@ \/ 'field')+-- rather than the bespoke @synthEq@ — a proof that the extension interface is+-- expressive enough to host a real, field-recursive synthesizer. Produces the+-- same Core as @synthEq@.+conPrec :: DataCon -> TcPluginM Integer+conPrec dc = do+#if MIN_VERSION_ghc(9,12,0)+ Fixity p _ <- unsafeTcPluginTcM (lookupFixityRn (dataConName dc))+#else+ Fixity _ p _ <- unsafeTcPluginTcM (lookupFixityRn (dataConName dc))+#endif+ pure (fromIntegral p)++-- | The default-method Id for the i-th method of a class (for filling+-- dictionary fields we don't override, via a recursive dictionary).+defMethId :: Class -> Int -> TcPluginM Id+defMethId cls i =+ case snd (classOpItems cls !! i) of+ Just (nm, _) -> tcLookupId nm+ Nothing -> error "stock: expected a default method"++-- | Synthesize an @Enum@ dictionary for an enumeration, mirroring GHC's+-- derived @Enum@: @fromEnum@ is the constructor tag, @toEnum@ uses+-- @tagToEnum#@. @succ@/@pred@/@enumFromTo@/@enumFromThenTo@ come from the+-- class default methods (correct and bounded); @enumFrom@/@enumFromThen@ are+-- overridden to stop at the last constructor (the defaults would run to+-- @maxBound::Int@ and crash).+data Variance = Cov | Con++flipV :: Variance -> Variance+flipV Cov = Con+flipV Con = Cov++-- | Build a variance-correct mapper for a field type @t@ between @t[pv:=src]@+-- and @t[pv:=tgt]@ (where @src@\/@tgt@ are the actual @a@\/@b@ types). This is+-- GHC's @DeriveFunctor@ algorithm: recurse through function arrows flipping+-- variance, and through covariant functor (or contravariant) applications.+--+-- * @Cov t@ yields @t[src] -> t[tgt]@; @Con t@ yields @t[tgt] -> t[src]@.+-- * the bare parameter maps via @covFwd@ (resp. @conFwd@); the unavailable+-- direction is 'Nothing', so a parameter in the wrong position fails+-- cleanly (e.g. a bare @a@ in a negative position is not a 'Functor').+-- * @fmapCls@ supplies @fmap@ for covariant subfields; @mContraCls@, if given,+-- supplies @contramap@ for contravariant subfields.+varMap :: Class -> Maybe Class -> CtLoc -> TyVar -> Type+ -> Maybe CoreExpr -> Maybe CoreExpr+ -> Variance -> Type -> TcPluginM (Maybe (CoreExpr, [Ct]))+varMap fmapCls mContraCls loc pv tgt covFwd conFwd =+ varMapN fmapCls mContraCls loc [(pv, tgt, covFwd, conFwd)] Nothing++-- | The n-ary variance engine behind 'varMap' (and so behind @Functor@,+-- @Contravariant@, @Bifunctor@, @Profunctor@, @Invariant@, …, which are this+-- one recursion at different /variance vectors/). Each parameter carries its+-- own detection tyvar (the source instantiation it appears as in the field),+-- its target type, and the two directional mappers — @covFwd@ for a covariant+-- occurrence (a @src -> tgt@), @conFwd@ for a contravariant one (a @tgt ->+-- src@); the unavailable direction is 'Nothing', so a parameter used against+-- its declared variance fails cleanly. A covariant slot populates @covFwd@+-- only, a contravariant slot @conFwd@ only, an invariant slot both. The+-- recursion is GHC's @DeriveFunctor@ algorithm (arrows flip variance,+-- last-argument functor\/contravariant applications recurse), now substituting+-- /all/ parameters at once.+varMapN :: Class -> Maybe Class -> CtLoc+ -> [(TyVar, Type, Maybe CoreExpr, Maybe CoreExpr)]+ -> Maybe (Type -> TcPluginM (Maybe (CoreExpr, [Ct])))+ -> Variance -> Type -> TcPluginM (Maybe (CoreExpr, [Ct]))+varMapN fmapCls mContraCls loc params mSelf = go+ where+ fmapSel = classMethod "fmap" fmapCls+ pvs = [ pv | (pv, _, _, _) <- params ]+ tgts = [ tgt | (_, tgt, _, _) <- params ]+ sub t = substTyWith pvs tgts t -- t[srcs:=tgts]+ inA t = any (`elemVarSet` tyCoVarsOfType t) pvs+ -- if @t@ is exactly one parameter's source tyvar, its directional mapper+ bareFwd t v = case [ (cf, conf) | (p, _, cf, conf) <- params, t `eqType` mkTyVarTy p ] of+ ((cf, conf) : _) -> Just (case v of Cov -> cf; Con -> conf)+ [] -> Nothing+ -- the spine of an application: @(head, [arg₁ .. argₖ])@+ spine ty = case splitAppTy_maybe ty of+ Just (f, a) -> let (h, as) = spine f in (h, as ++ [a])+ Nothing -> (ty, [])+ -- a self-application @q src₁ .. srcₙ@: @q@ (the head applied to any leading+ -- fixed args) is parameter-free and the trailing @n@ args are exactly our+ -- @n@ source tyvars in order, so @q@'s own n-ary map (the same class we are+ -- deriving) carries it — e.g. a @pro a b@ field under @Profunctor@.+ matchSelf ty =+ let (h, args) = spine ty+ n = length params+ in if length args >= n+ then let (pre, tl) = splitAt (length args - n) args+ qhead = mkAppTys h pre+ in if and (zipWith eqType tl (map mkTyVarTy pvs)) && not (inA qhead)+ then Just qhead else Nothing+ else Nothing+ go v t+ | not (inA t) = do x <- freshId t "x"; pure (Just (Lam x (Var x), [])) -- id+ | Just mfwd <- bareFwd t v = pure (fmap (\e -> (e, [])) mfwd)+ | Just (_, _, s, r) <- splitFunTy_maybe t = do+ ms <- go (flipV v) s -- argument flips variance+ mr <- go v r+ case (ms, mr) of+ (Just (es, w1), Just (er, w2)) -> do+ let (sf, rf) = case v of Cov -> (s, r); Con -> (sub s, sub r)+ xTy = case v of Cov -> sub s; Con -> s+ g <- freshId (mkVisFunTyMany sf rf) "g"+ x <- freshId xTy "x"+ pure (Just (mkLams [g, x] (App er (App (Var g) (App es (Var x)))), w1 ++ w2))+ _ -> pure Nothing+ -- tuple: the one place the parameter may appear in several arguments —+ -- GHC's @ft_tup@ maps every component pointwise (not via @Bifunctor@):+ -- @\\(x1,..,xn) -> (m1 x1, .., mn xn)@.+ | Cov <- v, Just (tc, args) <- splitTyConApp_maybe t+ , isTupleTyCon tc, length args >= 2 = do+ ms <- mapM (go Cov) args+ case sequence ms of+ Nothing -> pure Nothing+ Just pairs -> do+ let (mappers, wss) = unzip pairs+ dc = tupleDataCon Boxed (length args)+ xs <- mapM (`freshId` "u") args+ tup <- freshId t "tup" ; cb <- freshId t "cb"+ let body = mkCoreConApps dc (map (Type . sub) args ++ zipWith App mappers (map Var xs))+ pure (Just (Lam tup (Case (Var tup) cb (sub t) [Alt (DataAlt dc) xs body]), concat wss))+ | Just self <- mSelf, Cov <- v, Just q <- matchSelf t = self q+ | Just (h, larg) <- splitAppTy_maybe t, not (inA h) = do+ mf <- go v larg -- try H as a covariant functor+ case mf of+ Just (e, w) -> do+ ev <- newWanted loc (mkClassPred fmapCls [h])+ let (ft, tt) = case v of Cov -> (larg, sub larg); Con -> (sub larg, larg)+ pure (Just ( mkApps (Var fmapSel) [Type h, ctEvExpr ev, Type ft, Type tt, e]+ , mkNonCanonical ev : w ))+ Nothing -> case mContraCls of -- else try H as a contravariant functor+ Nothing -> pure Nothing+ Just contraCls -> do+ mc <- go (flipV v) larg+ case mc of+ Nothing -> pure Nothing+ Just (e, w) -> do+ ev <- newWanted loc (mkClassPred contraCls [h])+ -- contramap :: (x->y) -> f y -> f x+ let (xT, yT) = case v of Cov -> (sub larg, larg); Con -> (larg, sub larg)+ pure (Just ( mkApps (Var (classMethod "contramap" contraCls))+ [Type h, ctEvExpr ev, Type xT, Type yT, e]+ , mkNonCanonical ev : w ))+ | otherwise = pure Nothing++-- | Destructure a scrutinee of inner type @F instTys@ (already coerced to+-- @F instTys@) into per-constructor alternatives. A @data@ type becomes a real+-- @Case@; a @newtype@ has no runtime constructor — its single \"constructor\" is+-- a zero-cost coercion — so we unwrap the one field with a cast instead (a+-- @DataAlt@ on a newtype is rejected by Core Lint).+destructInner :: TyCon -> [Type] -> CoreExpr -> Id -> Type -> [CoreAlt] -> CoreExpr+destructInner fTc instTys scrut cb resTy alts+ | isNewTyCon fTc+ , [Alt _ [x] body] <- alts+ = Let (NonRec x (Cast scrut (mkUnbranchedAxInstCo Representational+ (newTyConCo fTc) instTys []))) body+ | otherwise = Case scrut cb resTy alts++-- | Synthesize @Functor (Stock1 F)@ — the covariant instance of the shared+-- @synthMap1@ engine.+freshTyVar :: String -> TcPluginM TyVar+freshTyVar = freshTyVarK liftedTypeKind++-- | A fresh type variable of the given kind.+freshTyVarK :: Kind -> String -> TcPluginM TyVar+freshTyVarK k s = do+ u <- unsafeTcPluginTcM getUniqueM+ pure (mkTyVar (mkSystemName u (mkTyVarOcc s)) k)++-- | Extract the 'CoreExpr' from the @EvExpr@ forms we build.+unwrapEv :: EvTerm -> CoreExpr+unwrapEv (EvExpr e) = e+unwrapEv _ = error "stock: expected EvExpr"++-- ----- shared ReadPrec assembler (GHC-faithful Read / Read1 / Read2) -------+--+-- GHC's derived @Read@ defines @readPrec@ (not @readsPrec@); @readsPrec@ comes+-- from the class default @readPrec_to_S readPrec@. Building the very same+-- @readPrec@ (same combinators, same @+++@ order) makes the synthesized+-- instance byte-faithful, including the order of ambiguous infix parses.++-- | Every combinator GHC's derived @readPrec@ uses, looked up once.+data ReadPrecEnv = ReadPrecEnv+ { rpReadPrecTc :: TyCon+ , rpMonadDict :: CoreExpr+ , rpBindSel, rpThenSel, rpReturnSel :: Id+ , rpParens, rpChoose, rpExpectP, rpReadField :: Id+ , rpPrec, rpStep, rpReset, rpPlus, rpPfail :: Id+ , rpIdentCon, rpSymbolCon, rpPuncCon :: DataCon+ }++-- | Look up the @ReadPrec@ combinators and request a @Monad ReadPrec@ wanted+-- (returned as the second component, to be emitted alongside the synthesized+-- instance's other wanteds).+lookupReadPrecEnv :: CtLoc -> TcPluginM (ReadPrecEnv, Ct)+lookupReadPrecEnv loc = do+ monadCls <- tcLookupClass monadClassName+ readPrecTc <- lookupOrig tEXT_READPREC (mkTcOcc "ReadPrec") >>= tcLookupTyCon+ parensId <- lookupOrig gHC_INTERNAL_READ (mkVarOcc "parens") >>= tcLookupId+ chooseId <- lookupOrig gHC_INTERNAL_READ (mkVarOcc "choose") >>= tcLookupId+ expectPId <- lookupOrig gHC_INTERNAL_READ (mkVarOcc "expectP") >>= tcLookupId+ readFieldId <- lookupOrig gHC_INTERNAL_READ (mkVarOcc "readField") >>= tcLookupId+ precId <- lookupOrig tEXT_READPREC (mkVarOcc "prec") >>= tcLookupId+ stepId <- lookupOrig tEXT_READPREC (mkVarOcc "step") >>= tcLookupId+ resetId <- lookupOrig tEXT_READPREC (mkVarOcc "reset") >>= tcLookupId+ plusId <- lookupOrig tEXT_READPREC (mkVarOcc "+++") >>= tcLookupId+ pfailId <- lookupOrig tEXT_READPREC (mkVarOcc "pfail") >>= tcLookupId+ identCon <- lookupOrig tEXT_READ_LEX (mkDataOcc "Ident") >>= tcLookupDataCon+ symbolCon <- lookupOrig tEXT_READ_LEX (mkDataOcc "Symbol") >>= tcLookupDataCon+ puncCon <- lookupOrig tEXT_READ_LEX (mkDataOcc "Punc") >>= tcLookupDataCon+ monadEv <- newWanted loc (mkClassPred monadCls [mkTyConTy readPrecTc])+ pure ( ReadPrecEnv readPrecTc (ctEvExpr monadEv)+ (classMethod ">>=" monadCls) (classMethod ">>" monadCls) (classMethod "return" monadCls)+ parensId chooseId expectPId readFieldId precId stepId resetId plusId pfailId+ identCon symbolCon puncCon+ , mkNonCanonical monadEv )++-- | Assemble a @readPrec@-shaped body for element type @gTy@. Each constructor+-- carries one /raw/ field reader (a @ReadPrec ft@) per field; this wraps them+-- exactly as GHC: nullary cons grouped into one leading @choose@, then prefix+-- (@prec 10@ + @step@) \/ infix (@prec fixity@ + @step@) \/ record (@prec 11@ ++-- @readField name (reset _)@) cons in declaration order, all under @parens@.+-- @mkConVal dc binders@ builds the (already wrapped\/cast) constructor value.+buildReadPrecBody :: ReadPrecEnv -> Type -> (DataCon -> [Id] -> CoreExpr)+ -> [(DataCon, [(Type, CoreExpr)])] -> TcPluginM CoreExpr+buildReadPrecBody env gTy mkConVal cons = do+ let ReadPrecEnv readPrecTc monadDict bindSel thenSel returnSel+ parensId chooseId expectPId readFieldId precId stepId resetId plusId pfailId+ identCon symbolCon puncCon = env+ readPrecTy = mkTyConTy readPrecTc+ strPairTy = mkBoxedTupleTy [stringTy, mkTyConApp readPrecTc [gTy]]+ bindP a b m k = mkApps (Var bindSel) [Type readPrecTy, monadDict, Type a, Type b, m, k]+ thenP a b m n = mkApps (Var thenSel) [Type readPrecTy, monadDict, Type a, Type b, m, n]+ returnP a v = mkApps (Var returnSel) [Type readPrecTy, monadDict, Type a, v]+ seqW m n = thenP unitTy gTy m n+ parensE a p = mkApps (Var parensId) [Type a, p]+ precE a n p = mkApps (Var precId) [Type a, mkUncheckedIntExpr n, p]+ stepE a p = mkApps (Var stepId) [Type a, p]+ resetE a p = mkApps (Var resetId) [Type a, p]+ plusE a p q = mkApps (Var plusId) [Type a, p, q]+ chooseE a xs = mkApps (Var chooseId) [Type a, xs]+ readFieldE a s p = mkApps (Var readFieldId) [Type a, s, p]+ expectPE l = App (Var expectPId) l+ identE s = mkCoreConApps identCon [s]+ symbolE s = mkCoreConApps symbolCon [s]+ puncE s = mkCoreConApps puncCon [s]+ str s = unsafeTcPluginTcM (mkStringExprFS (fsLit s))+ entries <- for cons \(dc, readers) -> do+ let name = occNameString (getOccName dc)+ labels = map (occNameString . nameOccName . flSelector) (dataConFieldLabels dc)+ nameE <- str name+ argIds <- zipWithM (\(ft, _) i -> freshId ft ("a" ++ show (i :: Int))) readers [0 ..]+ let ret = returnP gTy (mkConVal dc argIds)+ items = zip3 argIds (map fst readers) (map snd readers) -- (binder, ft, rawReader)+ if null readers+ then pure (Left (nameE, ret)) -- nullary -> choose entry+ else if dataConIsInfix dc+ then do+ prec <- conPrec dc+ let [(a0, ft0, rd0), (a1, ft1, rd1)] = items+ inner = bindP ft0 gTy (stepE ft0 rd0) $ Lam a0 $+ seqW (expectPE (symbolE nameE)) $+ bindP ft1 gTy (stepE ft1 rd1) (Lam a1 ret)+ pure (Right (precE gTy prec inner))+ else if not (null labels)+ then do+ openCE <- str "{"; closeCE <- str "}"; commaCE <- str ","+ lblEs <- mapM str labels+ let closeRet = seqW (expectPE (puncE closeCE)) ret+ go [] = closeRet+ go ((i, lblE, (aId, ft, rd)) : rest) =+ let bound = bindP ft gTy (readFieldE ft lblE (resetE ft rd)) (Lam aId (go rest))+ in if i == (0 :: Int) then bound else seqW (expectPE (puncE commaCE)) bound+ inner = seqW (expectPE (identE nameE)) $+ seqW (expectPE (puncE openCE)) $+ go (zip3 [0 ..] lblEs items)+ pure (Right (precE gTy 11 inner))+ else do -- prefix with args+ let chain = foldr (\(aId, ft, rd) acc -> bindP ft gTy (stepE ft rd) (Lam aId acc)) ret items+ inner = seqW (expectPE (identE nameE)) chain+ pure (Right (precE gTy 10 inner))+ let nullaries = [e | Left e <- entries]+ others = [p | Right p <- entries]+ chooseP = chooseE gTy (mkListExpr strPairTy [ mkCoreTup [n, p] | (n, p) <- nullaries ])+ allP = [chooseP | not (null nullaries)] ++ others+ combined = case allP of+ [] -> mkApps (Var pfailId) [Type gTy]+ [p] -> p+ ps -> foldr1 (plusE gTy) ps+ pure (parensE gTy combined)
+ plugin/Stock/Ord.hs view
@@ -0,0 +1,230 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE DerivingVia #-}+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns -Wno-unused-imports #-}+-- | @Ord@ synthesizer: tag order between constructors, lexicographic within.+module Stock.Ord where+-- Most names below (data-con/type builders, coercion builders, occ-name+-- helpers, …) are re-exported by 'GHC.Plugins', so we only import explicitly+-- the ones it does not provide.+import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Tc.Utils.Monad (addErrTc)+import GHC.Tc.Errors.Types (mkTcRnUnknownMessage)+import GHC.Types.Error (mkPlainError, noHints)+import GHC.Core.Class (Class, className, classMethods, classOpItems, classTyCon)+import GHC.Core.Predicate (classifyPredType, Pred(ClassPred), mkClassPred)+import GHC.Builtin.Types.Prim (intPrimTy)+import GHC.Builtin.PrimOps (PrimOp(TagToEnumOp))+import GHC.Builtin.PrimOps.Ids (primOpId)+import GHC.Builtin.Names ( eqClassName, ordClassName, appendName+ , enumClassName, mapName, numClassName+ , enumFromToName, enumFromThenToName+ , eqStringName+ , genClassName, repTyConName, u1TyConName, k1TyConName+ , prodTyConName, sumTyConName+ , monoidClassName, foldableClassName, functorClassName+ , semigroupClassName )+import Stock.Compat ( gHC_INTERNAL_SHOW, gHC_INTERNAL_READ+ , gHC_INTERNAL_LIST, gHC_INTERNAL_GENERICS )+import GHC.Core.Reduction (mkReduction)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import GHC.Rename.Fixity (lookupFixityRn)+import GHC.Types.Fixity (Fixity(..), defaultFixity)+import GHC.Core.TyCo.Compare (eqType)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.SimpleOpt (defaultSimpleOpts)+import GHC.Core.Unfold.Make (mkInlineUnfoldingWithArity)+import GHC.Core.InstEnv (classInstances, is_dfun, is_tys)+import GHC.Runtime.Loader (getValueSafely)+import Stock.Derive+import Data.Maybe (catMaybes, fromJust, isJust, fromMaybe)+import qualified Data.Monoid as Mon (Alt(..)) -- 'Alt' clashes with GHC.Core's case-alt 'Alt'+import Stock.Trans (MaybeT(..))+import Control.Monad (forM, zipWithM, unless, guard)+import Data.IORef (IORef, newIORef, readIORef, modifyIORef')+import Stock.Internal+import Stock.Eq++buildCompare :: CtLoc -> Type -> Type -> Coercion -> [(DataCon, [Coercion])]+ -> TcPluginM (CoreExpr, [Ct])+buildCompare loc wrappedTy innerTy co dcons = do+ ordCls <- tcLookupClass ordClassName+ let ordTy = mkTyConTy orderingTyCon+ [ltC, eqC, gtC] = tyConDataCons orderingTyCon+ ltE = Var (dataConWorkId ltC); eqE = Var (dataConWorkId eqC); gtE = Var (dataConWorkId gtC)+ cmpSel = classMethod "compare" ordCls -- compare+ scrut v = Cast (Var v) co+ indexed = zip [0 :: Int ..] dcons+ -- bind the field at its real type; compare it at the (override) modifier+ -- type, coercing the value — 'Refl' (no override) makes this a no-op.+ realFts dc = fieldTysAt innerTy dc++ -- lexicographic compare of equally-tagged fields (per field: its+ -- override coercion + the two bound field ids)+ lexCmp [] = pure (eqE, [])+ lexCmp ((fco, x, y) : more) = do+ let ft = coercionRKind fco -- modifier type (real type if Refl)+ ev <- newWanted loc (mkClassPred ordCls [ft])+ (restE, ws) <- lexCmp more+ scr <- freshId ordTy "o"+ let cmp = mkApps (Var cmpSel) [Type ft, ctEvExpr ev, castInto (Var x) fco, castInto (Var y) fco]+ e = Case cmp scr ordTy+ [ Alt (DataAlt ltC) [] ltE+ , Alt (DataAlt eqC) [] restE+ , Alt (DataAlt gtC) [] gtE ]+ pure (e, mkNonCanonical ev : ws)++ aId <- freshId wrappedTy "a"+ bId <- freshId wrappedTy "b"+ (outerAlts, wss) <- fmap unzip $ forM indexed \(i, (dci, cosI)) -> do+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] (realFts dci)+ (innerAlts, iwss) <- fmap unzip $ forM indexed \(j, (dcj, _)) -> do+ ys <- zipWithM (\n ft -> freshId ft ("y" ++ show n)) [0 :: Int ..] (realFts dcj)+ (body, ws) <- if i == j+ then lexCmp (zip3 cosI xs ys)+ else pure (if i < j then ltE else gtE, [])+ pure (Alt (DataAlt dcj) ys body, ws)+ innerBndr <- freshId innerTy "cb"+ pure (Alt (DataAlt dci) xs (Case (scrut bId) innerBndr ordTy innerAlts), concat iwss)+ outerBndr <- freshId innerTy "ca"+ let cmpImpl = mkLams [aId, bId] (Case (scrut aId) outerBndr ordTy outerAlts)+ pure (cmpImpl, concat wss)++-- | A direct relational op @a -> b -> Bool@, matching GHC's derived+-- @\<@\/@\<=@\/@\>@\/@\>=@ for small types (it does NOT build an @Ordering@):+-- different constructors compare by tag, equal constructors lexicographically+-- @x1 \`fop\` y1 || (x1 == y1 && rest)@. @asc@ = ascending (@\<@\/@\<=@); @refl@+-- = reflexive (@\<=@\/@\>=@, so the final field and the nullary case include+-- equality). The non-final fields use the strict op (@\<@\/@\>@) + @==@; the+-- final field uses the actual op.+buildRel :: Class -> Class -> CtLoc -> Type -> Type -> Coercion -> [(DataCon, [Coercion])]+ -> Bool -> Bool -> TcPluginM (CoreExpr, [Ct])+buildRel ordCls eqCls loc wrappedTy innerTy co dcons asc refl = do+ let boolE b = Var (dataConWorkId (if b then trueDataCon else falseDataCon))+ ltName = if asc then "<" else ">"+ lastName | asc && not refl = "<" | asc = "<=" | not refl = ">" | otherwise = ">="+ scrut v = Cast (Var v) co+ realFts dc = fieldTysAt innerTy dc+ indexed = zip [0 :: Int ..] dcons+ fieldRel nm fco x y = do+ let ft = coercionRKind fco+ ev <- newWanted loc (mkClassPred ordCls [ft])+ pure ( mkApps (Var (classMethod nm ordCls))+ [Type ft, ctEvExpr ev, castInto (Var x) fco, castInto (Var y) fco]+ , [mkNonCanonical ev] )+ fieldEq fco x y = do+ let ft = coercionRKind fco+ ev <- newWanted loc (mkClassPred eqCls [ft])+ pure ( mkApps (Var (classMethod "==" eqCls))+ [Type ft, ctEvExpr ev, castInto (Var x) fco, castInto (Var y) fco]+ , [mkNonCanonical ev] )+ orE p q = do s <- freshId boolTy "o"+ pure (Case p s boolTy [ Alt (DataAlt falseDataCon) [] q+ , Alt (DataAlt trueDataCon) [] (boolE True) ])+ andE2 p q = do s <- freshId boolTy "n"+ pure (Case p s boolTy [ Alt (DataAlt falseDataCon) [] (boolE False)+ , Alt (DataAlt trueDataCon) [] q ])+ lexRel [] = pure (boolE refl, [])+ lexRel [(fco, x, y)] = fieldRel lastName fco x y+ lexRel ((fco, x, y) : more) = do+ (ltE, w1) <- fieldRel ltName fco x y+ (eqE, w2) <- fieldEq fco x y+ (rest, w3) <- lexRel more+ ae <- andE2 eqE rest+ oe <- orE ltE ae+ pure (oe, w1 ++ w2 ++ w3)+ aId <- freshId wrappedTy "a" ; bId <- freshId wrappedTy "b"+ (outerAlts, wss) <- fmap unzip $ forM indexed \(i, (dci, cosI)) -> do+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] (realFts dci)+ (innerAlts, iwss) <- fmap unzip $ forM indexed \(j, (dcj, _)) -> do+ ys <- zipWithM (\n ft -> freshId ft ("y" ++ show n)) [0 :: Int ..] (realFts dcj)+ (body, ws) <- if i == j then lexRel (zip3 cosI xs ys)+ else pure (boolE (if asc then i < j else i > j), [])+ pure (Alt (DataAlt dcj) ys body, ws)+ cb <- freshId innerTy "cb"+ pure (Alt (DataAlt dci) xs (Case (scrut bId) cb boolTy innerAlts), concat iwss)+ cb2 <- freshId innerTy "ca"+ pure (mkLams [aId, bId] (Case (scrut aId) cb2 boolTy outerAlts), concat wss)++-- | Synthesize a structural @Eq (Stock Inner)@ dictionary for any single-level+-- algebraic @Inner@. Two values are equal iff they share a constructor and all+-- corresponding fields are equal; field equality uses each field type's own+-- @Eq@ dictionary, requested as a fresh wanted constraint.+-- | Bridge the internal @Repr@ EDSL to the public @Datatype@ view handed to+-- SDK derivers.+synthOrd :: Class -> CtLoc -> Type -> Type -> Coercion -> [(DataCon, [Coercion])]+ -> TcPluginM (EvTerm, [Ct])+synthOrd ordCls loc wrappedTy innerTy co dcons = do+ (cmpImpl, cmpWs) <- buildCompare loc wrappedTy innerTy co dcons++ -- Eq superclass dictionary (also field-aware).+ eqCls <- tcLookupClass eqClassName+ (eqDict0, eqWs) <- synthEq eqCls loc wrappedTy innerTy co dcons+ let eqDict = unwrapEv eqDict0++ -- Override only @compare@ (the minimal complete definition) and let the+ -- class default methods supply @(<)@, @(<=)@, @(>)@, @(>=)@, @max@, @min@ —+ -- exactly as a hand-written @instance Ord T where compare = …@ would. We+ -- give @compare@ an INLINE (stable) unfolding so GHC can inline it into the+ -- derived operators (and into specialising consumers), matching how it treats+ -- a source-written instance method.+ --+ -- Note on performance: when the consumer can specialise to the type (the+ -- common case, and everything that inlines — @map (fmap …)@, a user+ -- @sortBy@, etc.) this is byte-for-byte identical to stock @deriving@. A+ -- residual ~15-20% remains only when feeding comparisons to a *pre-compiled,+ -- non-specialising* consumer such as @Data.List.sort@, which calls the @Ord@+ -- method indirectly; that overhead is inherent to GHC's dictionary handling,+ -- not to the synthesized comparison (its worker is identical to stock's).+ -- With an Override the field coercions are still-unsolved holes; running the+ -- simple optimiser (inside 'mkInlineUnfoldingWithArity') over Core that+ -- mentions them panics @optCoercion@. So give @compare@ the INLINE unfolding+ -- only in the (common) non-override case — there the Core is identical to+ -- before; overridden types get the plain inlined method (no eager opt).+ let overridden = any (not . isReflCo) (concatMap snd dcons)+ -- GHC's "game plan": for small types (<=3 constructors, or an+ -- enumeration) define <,<=,>,>= DIRECTLY (not via compare), closing the+ -- ~15-20% residual on non-specialising consumers like Data.List.sort.+ small = length dcons <= 3 || all (null . snd) dcons+ idxOf nm = head [ i | (i, m) <- zip [0 :: Int ..] (classMethods ordCls)+ , occNameString (occName m) == nm ]+ (relOverrides, relWs) <-+ if not small then pure ([], [])+ else do+ let mk asc refl = buildRel ordCls eqCls loc wrappedTy innerTy co dcons asc refl+ (ltI, w1) <- mk True False ; (leI, w2) <- mk True True+ (gtI, w3) <- mk False False ; (geI, w4) <- mk False True+ pure ( [(idxOf "<", ltI), (idxOf "<=", leI), (idxOf ">", gtI), (idxOf ">=", geI)]+ , w1 ++ w2 ++ w3 ++ w4 )+ if overridden+ then do+ dict <- recDictWith ordCls wrappedTy [eqDict] ([(0, cmpImpl)] ++ relOverrides)+ pure (EvExpr dict, cmpWs ++ eqWs ++ relWs)+ else do+ let cmpTy = mkVisFunTyMany wrappedTy (mkVisFunTyMany wrappedTy (mkTyConTy orderingTyCon))+ cmpUnf = mkInlineUnfoldingWithArity defaultSimpleOpts StableSystemSrc 2 cmpImpl+ cmpId0 <- freshId cmpTy "vvCompare"+ let cmpId = cmpId0 `setIdUnfolding` cmpUnf+ dictInner <- recDictWith ordCls wrappedTy [eqDict] ([(0, Var cmpId)] ++ relOverrides)+ let dict = Let (NonRec cmpId cmpImpl) dictInner+ pure (EvExpr dict, cmpWs ++ eqWs ++ relWs)++-- | Synthesize a @Show@ dictionary matching GHC's derived @Show@, for prefix+-- (non-record, non-infix) constructors. Per the Haskell Report algorithm:+--+-- nullary: showsPrec _ K = showString "K"+-- n-ary: showsPrec d (K a..) = showParen (d > 10)+-- ( showString "K" . showSpace . showsPrec 11 a . showSpace . ... )+--+-- Field rendering is delegated to each field's own @showsPrec@ at precedence+-- 11, so nesting, negative numbers, etc. match exactly.
+ plugin/Stock/Override.hs view
@@ -0,0 +1,143 @@+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE TypeFamilies #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE StandaloneKindSignatures #-}+-- | Per-field deriving modifiers for the Stock plugin.+--+-- @Override a cfg@ wraps @a@ with a type-level configuration @cfg@ that the+-- plugin reads while it synthesizes the instance: each entry names a field and+-- the modifier to run on it (per-field @DerivingVia@). At runtime @Override@ is+-- just @a@ (a newtype), so there is no cost.+--+-- > data Coord = Coord { x :: Int, y :: Int }+-- > deriving Semigroup+-- > via Stock (Override Coord '[ "x" ':= Sum, "y" ':= Product ])+--+-- The config is an uninterpreted, poly-kinded marker the solver decodes off the+-- type — never reduced. See @docs\/override-design.md@.+--+-- == Addressing a field+--+-- A field is addressed by name (@\"x\" ':= m@), by type (@Int ':= m@, every+-- @Int@ field), or by position (@'At' Coord 0 ':= m@). A modifier is /pinned/+-- (@Sum Int@) or /broadcast/ to the field's own type (@Sum@). A whole entry+-- may instead be positional — one inner list per constructor, one cell per+-- field — where 'Keep' (written @_@) leaves a field untouched:+--+-- > deriving Semigroup via Stock (Override Coord '[ [Sum, Keep] ]) -- field 0 via Sum+--+-- == Surface sugar (the @-fplugin Stock@ source pass, "Stock.Surface")+--+-- The honest marker form is verbose, so the same plugin lowers a quote-free+-- surface at parse time, /scoped to @Override@ applications/:+--+-- > Override Coord [ x via Sum, Coord at 0 via Sum, _ ] -- what you write+-- > Override Coord '[ "x" := Sum, At Coord 0 := Sum, Keep ] -- what the solver reads+--+-- namely: a bare lowercase selector becomes a @Symbol@ (@x@ ⟶ @\"x\"@), @via@+-- becomes ':=', @at@ becomes 'At', and a wildcard @_@ becomes 'Keep'.+--+-- == Higher order+--+-- 'Override1' \/ 'Override2' reshape the /functor/ of a field rather than its+-- element type (an @h a@ field becomes @m a@), so the lifted instance+-- (@Functor@, @Eq1@, @Applicative@, …) uses @m@'s method. See 'Override1'.+module Stock.Override+ ( Override(..)+ , Overriding+ , Override1(..)+ , Overriding1+ , Override2(..)+ , Overriding2+ , type (:=)+ , type (-->)+ , At+ , Keep+ ) where++import Data.Kind (Type)+import GHC.TypeLits (Nat)+import Stock.Type (Stock, Stock1, Stock2)++-- | @Overriding a cfg = Stock (Override a cfg)@ — the per-field wrapper read+-- through @Generically@. Because the plugin makes @Generic@ honour @Override@+-- (the @Rep@ carries the modifier field types), @deriving C via Generically+-- (Overriding A cfg)@ derives /any/ @Generic@-based class over @A@ with the+-- per-field modifiers applied. The Generic-facing twin of using 'Stock' ++-- 'Override' directly with the built-in synthesizers.+type Overriding :: forall k. Type -> k -> Type+type Overriding a cfg = Stock (Override a cfg)++-- | The one-parameter analogue of 'Override': @Override1 f cfg@ wraps a+-- one-parameter constructor @f@ for use through @Stock1@. Each positional+-- modifier @m@ (a @k -> Type@) reshapes the /functor/ of an @h a@ field to+-- @m a@ — so e.g. a @[a]@ field becomes @ZipList a@ and the derived+-- @Applicative@ zips instead of taking the cartesian product. A newtype, so+-- @Coercible (Override1 f cfg a) (f a)@.+type Override1 :: forall k j. (j -> Type) -> k -> (j -> Type)+newtype Override1 f cfg a = Override1 (f a)++-- | @Overriding1 f cfg = Stock1 (Override1 f cfg)@ — the @Stock1@-facing+-- per-field wrapper. @deriving Applicative via Overriding1 F '[ '[ZipList] ]@+-- reshapes @F@'s @[a]@ field into @ZipList a@ before deriving.+type Overriding1 :: forall k j. (j -> Type) -> k -> (j -> Type)+type Overriding1 f cfg = Stock1 (Override1 f cfg)++-- | The two-parameter analogue of 'Override': @Override2 p cfg@ wraps a+-- two-parameter constructor @p@ for use through @Stock2@. Each positional+-- modifier @m@ (a @Type -> Type -> Type@) reshapes its field to @m a b@ — the+-- modifier applied to /both/ datatype parameters — turning the field into a+-- per-field @Category@. A newtype, so @Coercible (Override2 p cfg a b) (p a b)@.+type Override2 :: forall k. (Type -> Type -> Type) -> k -> (Type -> Type -> Type)+newtype Override2 p cfg a b = Override2 (p a b)++-- | @Overriding2 p cfg = Stock2 (Override2 p cfg)@ — the @Stock2@-facing+-- per-field wrapper. @deriving Category via Overriding2 '[ '[Basic (Sum Int),+-- Basic String, Kleisli Maybe] ] Foo@ reshapes each field of @Foo a b@ into a+-- @Category@ and derives @Category@ pointwise over them.+type Overriding2 :: forall k. (Type -> Type -> Type) -> k -> (Type -> Type -> Type)+type Overriding2 p cfg = Stock2 (Override2 p cfg)++-- | @a@ with a per-field override configuration @cfg@. A newtype, so+-- @Coercible (Override a cfg) a@; @cfg@ is phantom (read by the plugin only).+-- Poly-kinded in @cfg@ so it accepts both config shapes (see+-- @docs\/override-design.md@ §5a): the /entry list/ @'[ "x" ':= Sum, … ]@+-- (@cfg :: [Type]@) and the /positional/ @'[ '[Sum Int, Keep, Keep] ]@ — one+-- inner list per constructor, one element per field (@cfg :: [[Type]]@).+type Override :: forall k. Type -> k -> Type+newtype Override a cfg = Override a++-- | The positional no-op modifier: a field whose slot is @Keep@ is left at its+-- own type. Written @_@ in source (the @-fplugin Stock@ surface pass lowers the+-- type wildcard to @Keep@), so @'[ '[Sum Int, _, _] ]@ overrides only the first+-- field. Poly-kinded (a free-result-kind 'data family') so it sits in a list+-- beside modifiers of /any/ kind — @Sum Int :: Type@ or @Sum :: Type -> Type@ —+-- without breaking the list's kind homogeneity. An uninterpreted marker the+-- plugin reads; never reduced.+type Keep :: forall k. k+data family Keep++-- | A single config entry: the field @name@ (a 'Symbol') gets modifier @m@.+-- Poly-kinded in @m@, so a saturated modifier (@Sum Int :: Type@) and an+-- unsaturated one (@Sum :: Type -> Type@) both fit; the plugin dispatches on+-- @m@'s kind (pin vs. broadcast). An uninterpreted 'data family' — generative,+-- injective, never reduced — so the solver reads it back verbatim.+type (:=) :: forall sel k. sel -> k -> Type+data family (:=) sel m++-- | A positional selector: field @pos@ of constructor @con@. Used /prefix/ on+-- the left of @(:=)@ — @At Con 0 := m@ — so the surface keeps a single infix+-- operator. Like '(:=)' it is an uninterpreted, poly-kinded marker.+type At :: forall kc sel. kc -> Nat -> sel+data family At con pos++-- | A path hop: @h '--> rest@. Each non-terminal hop selects a node — a+-- promoted constructor (that constructor), a 'Nat' (field by position) or a+-- 'Symbol' (field by label) — and the terminal hop is the modifier; the+-- modifier applies to every field under the prefix. So @'P '--> m@ overrides+-- every field of @P@ and @'P '--> 0 '--> m@ overrides only its first field+-- (design §4). Poly-kinded, uninterpreted, never reduced.+type (-->) :: forall k1 k2 j. k1 -> k2 -> j+data family (-->) a b+infixr 5 -->
+ plugin/Stock/Read.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE DerivingVia #-}+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns -Wno-unused-imports #-}+-- | @Read@ synthesizer: builds @readPrec@ exactly as GHC's derived @Read@ does+-- (the @ReadPrec@ combinators via "Stock.Internal"'s 'buildReadPrecBody'), so+-- @readsPrec@ — from the class default — is byte-faithful, including the order+-- of ambiguous infix parses.+module Stock.Read where+-- Most names below (data-con/type builders, coercion builders, occ-name+-- helpers, …) are re-exported by 'GHC.Plugins', so we only import explicitly+-- the ones it does not provide.+import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Tc.Utils.Monad (addErrTc)+import GHC.Tc.Errors.Types (mkTcRnUnknownMessage)+import GHC.Types.Error (mkPlainError, noHints)+import GHC.Core.Class (Class, className, classMethods, classOpItems, classTyCon)+import GHC.Core.Predicate (classifyPredType, Pred(ClassPred), mkClassPred)+import GHC.Builtin.Types.Prim (intPrimTy)+import GHC.Builtin.PrimOps (PrimOp(TagToEnumOp))+import GHC.Builtin.PrimOps.Ids (primOpId)+import GHC.Builtin.Names ( eqClassName, ordClassName, appendName+ , enumClassName, mapName, numClassName+ , enumFromToName, enumFromThenToName+ , eqStringName+ , genClassName, repTyConName, u1TyConName, k1TyConName+ , prodTyConName, sumTyConName+ , monoidClassName, foldableClassName, functorClassName+ , semigroupClassName, monadClassName )+import Stock.Compat ( gHC_INTERNAL_SHOW, gHC_INTERNAL_READ+ , gHC_INTERNAL_LIST, gHC_INTERNAL_GENERICS+ , tEXT_READPREC, tEXT_READ_LEX )+import GHC.Core.Reduction (mkReduction)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import GHC.Rename.Fixity (lookupFixityRn)+import GHC.Types.Fixity (Fixity(..), defaultFixity)+import GHC.Core.TyCo.Compare (eqType)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.SimpleOpt (defaultSimpleOpts)+import GHC.Core.Unfold.Make (mkInlineUnfoldingWithArity)+import GHC.Core.InstEnv (classInstances, is_dfun, is_tys)+import GHC.Runtime.Loader (getValueSafely)+import Stock.Derive+import Data.Maybe (catMaybes, fromJust, isJust, fromMaybe)+import qualified Data.Monoid as Mon (Alt(..)) -- 'Alt' clashes with GHC.Core's case-alt 'Alt'+import Stock.Trans (MaybeT(..))+import Control.Monad (forM, zipWithM, unless, guard)+import Data.IORef (IORef, newIORef, readIORef, modifyIORef')+import Stock.Internal++synthRead :: Class -> CtLoc -> Type -> Type -> Coercion -> [(DataCon, [Coercion])]+ -> TcPluginM (EvTerm, [Ct])+synthRead cls loc wrappedTy innerTy co dcons = do+ (env, monadCt) <- lookupReadPrecEnv loc+ let readPrecSel = classMethod "readPrec" cls+ toWrapped e = Cast e (mkSymCo co)+ -- each field is read at its modifier type @ft@ (= coercionRKind of its+ -- coercion) via that type's own @readPrec@, then coerced back to the real+ -- field type when the constructor is built.+ (cons, evss) <- fmap unzip $ forM dcons \(dc, cosI) -> do+ let fts = map coercionRKind cosI+ fieldEvs <- mapM (\ft -> newWanted loc (mkClassPred cls [ft])) fts+ let readers = [ (ft, mkApps (Var readPrecSel) [Type ft, ctEvExpr ev])+ | (ft, ev) <- zip fts fieldEvs ]+ pure ((dc, readers, cosI), fieldEvs)+ let cosMap = [ (getUnique dc, cosI) | (dc, _, cosI) <- cons ]+ mkConVal dc argIds =+ let cosI = fromJust (lookup (getUnique dc) cosMap)+ in toWrapped (conAppAt innerTy dc+ (zipWith (\a c -> castInto (Var a) (mkSymCo c)) argIds cosI))+ body <- buildReadPrecBody env wrappedTy mkConVal [ (dc, rs) | (dc, rs, _) <- cons ]+ dict <- recDictWith cls wrappedTy [] [(2, body)]+ pure (EvExpr dict, monadCt : map mkNonCanonical (concat evss))++-- | Synthesize @Generic (Stock T)@ for any single-level ADT. @Rep@ is a+-- balanced @:+:@ tree of constructors (one constructor ⇒ no @:+:@), each a+-- balanced @:*:@ tree of @Rec0 field@ (or @U1@ if no fields). @from@ matches+-- the real constructor, builds its product value and injects it into the sum+-- with @L1@\/@R1@; @to@ projects through the @:+:@\/@:*:@ structure and+-- rebuilds. All casts go through the same plugin coercion the rewriter+-- asserts. @K1@\/@:+:@\/@:*:@ layers: @K1@ is a newtype (coercion), @:+:@ and+-- @:*:@ are real data (constructed/matched).
+ plugin/Stock/Semigroup.hs view
@@ -0,0 +1,85 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE DerivingVia #-}+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns -Wno-unused-imports #-}+-- | @Semigroup@ \/ @Monoid@ synthesizers: pointwise over a single-constructor product.+module Stock.Semigroup where+-- Most names below (data-con/type builders, coercion builders, occ-name+-- helpers, …) are re-exported by 'GHC.Plugins', so we only import explicitly+-- the ones it does not provide.+import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Tc.Utils.Monad (addErrTc)+import GHC.Tc.Errors.Types (mkTcRnUnknownMessage)+import GHC.Types.Error (mkPlainError, noHints)+import GHC.Core.Class (Class, className, classMethods, classOpItems, classTyCon)+import GHC.Core.Predicate (classifyPredType, Pred(ClassPred), mkClassPred)+import GHC.Builtin.Types.Prim (intPrimTy)+import GHC.Builtin.PrimOps (PrimOp(TagToEnumOp))+import GHC.Builtin.PrimOps.Ids (primOpId)+import GHC.Builtin.Names ( eqClassName, ordClassName, appendName+ , enumClassName, mapName, numClassName+ , enumFromToName, enumFromThenToName+ , eqStringName+ , genClassName, repTyConName, u1TyConName, k1TyConName+ , prodTyConName, sumTyConName+ , monoidClassName, foldableClassName, functorClassName+ , semigroupClassName )+import Stock.Compat ( gHC_INTERNAL_SHOW, gHC_INTERNAL_READ+ , gHC_INTERNAL_LIST, gHC_INTERNAL_GENERICS )+import GHC.Core.Reduction (mkReduction)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import GHC.Rename.Fixity (lookupFixityRn)+import GHC.Types.Fixity (Fixity(..), defaultFixity)+import GHC.Core.TyCo.Compare (eqType)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.SimpleOpt (defaultSimpleOpts)+import GHC.Core.Unfold.Make (mkInlineUnfoldingWithArity)+import GHC.Core.InstEnv (classInstances, is_dfun, is_tys)+import GHC.Runtime.Loader (getValueSafely)+import Stock.Derive+import Data.Maybe (catMaybes, fromJust, isJust, fromMaybe)+import qualified Data.Monoid as Mon (Alt(..)) -- 'Alt' clashes with GHC.Core's case-alt 'Alt'+import Stock.Trans (MaybeT(..))+import Control.Monad (forM, zipWithM, unless, guard)+import Data.IORef (IORef, newIORef, readIORef, modifyIORef')+import Stock.Internal+-- @gmappend x y = productTypeTo (cliftA2_NP (Proxy \@Semigroup) (mapIII (<>))+-- (productTypeFrom x) (productTypeFrom y))@+semigroupDeriver :: Deriver+semigroupDeriver = Deriver \cls dt -> do+ let via = dtVia dt+ sappSel = classMethod "<>" cls -- (<>)+ mapSapp ft d x y = mkApps (Var sappSel) [Type ft, d, x, y]+ aId <- fresh via "a" ; bId <- fresh via "b"+ body <- fromProduct dt via (Var aId) \xs ->+ fromProduct dt via (Var bId) \ys ->+ toProduct dt <$> czipFields cls mapSapp (productCon dt) xs ys+ dict <- liftTc (recDictWith cls via [] [(0, mkLams [aId, bId] body)])+ pure (EvExpr dict)++-- | Pointwise @Monoid@ for a single-constructor product: @mempty = C mempty..@.+-- Its @Semigroup@ superclass is the 'semigroupDeriver' dictionary;+-- @mappend@\/@mconcat@ come from the class defaults.+--+-- @gmempty = productTypeTo (cpure_NP (Proxy \@Monoid) (I mempty))@+monoidDeriver :: Deriver+monoidDeriver = Deriver \cls dt -> do+ semigroupCls <- liftTc (tcLookupClass semigroupClassName)+ superEv <- runDeriver semigroupDeriver semigroupCls dt+ let via = dtVia dt+ memptySel = classMethod "mempty" cls -- mempty+ mapMempty ft d = mkApps (Var memptySel) [Type ft, d]+ memptyVal <- toProduct dt <$> cpureFields cls mapMempty (productCon dt)+ dict <- liftTc (recDictWith cls via [unwrapEv superEv] [(0, memptyVal)])+ pure (EvExpr dict)+
+ plugin/Stock/Show.hs view
@@ -0,0 +1,164 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE ViewPatterns #-}+{-# LANGUAGE DerivingVia #-}+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns -Wno-unused-imports #-}+-- | @Show@ synthesizer: GHC-faithful @showsPrec@ (prefix \/ infix \/ record, with parens).+module Stock.Show where+-- Most names below (data-con/type builders, coercion builders, occ-name+-- helpers, …) are re-exported by 'GHC.Plugins', so we only import explicitly+-- the ones it does not provide.+import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Tc.Utils.Monad (addErrTc)+import GHC.Tc.Errors.Types (mkTcRnUnknownMessage)+import GHC.Types.Error (mkPlainError, noHints)+import GHC.Core.Class (Class, className, classMethods, classOpItems, classTyCon)+import GHC.Core.Predicate (classifyPredType, Pred(ClassPred), mkClassPred)+import GHC.Builtin.Types.Prim (intPrimTy)+import GHC.Builtin.PrimOps (PrimOp(TagToEnumOp))+import GHC.Builtin.PrimOps.Ids (primOpId)+import GHC.Builtin.Names ( eqClassName, ordClassName, appendName+ , enumClassName, mapName, numClassName+ , enumFromToName, enumFromThenToName+ , eqStringName+ , genClassName, repTyConName, u1TyConName, k1TyConName+ , prodTyConName, sumTyConName+ , monoidClassName, foldableClassName, functorClassName+ , semigroupClassName )+import Stock.Compat ( gHC_INTERNAL_SHOW, gHC_INTERNAL_READ+ , gHC_INTERNAL_LIST, gHC_INTERNAL_GENERICS )+import GHC.Core.Reduction (mkReduction)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import GHC.Rename.Fixity (lookupFixityRn)+import GHC.Types.Fixity (Fixity(..), defaultFixity)+import GHC.Core.TyCo.Compare (eqType)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.SimpleOpt (defaultSimpleOpts)+import GHC.Core.Unfold.Make (mkInlineUnfoldingWithArity)+import GHC.Core.InstEnv (classInstances, is_dfun, is_tys)+import GHC.Runtime.Loader (getValueSafely)+import Stock.Derive+import Data.Maybe (catMaybes, fromJust, isJust, fromMaybe)+import qualified Data.Monoid as Mon (Alt(..)) -- 'Alt' clashes with GHC.Core's case-alt 'Alt'+import Stock.Trans (MaybeT(..))+import Control.Monad (forM, zipWithM, unless, guard)+import Data.IORef (IORef, newIORef, readIORef, modifyIORef')+import Stock.Internal++synthShow :: Class -> CtLoc -> Type -> Type -> Coercion -> [(DataCon, [Coercion])]+ -> TcPluginM (EvTerm, [Ct])+synthShow showCls loc wrappedTy innerTy co dcons = do+ appendId <- tcLookupId appendName+ showListName <- lookupOrig gHC_INTERNAL_SHOW (mkVarOcc "showList__")+ showList__Id <- tcLookupId showListName+ ordCls <- tcLookupClass ordClassName++ let showsPrecSel = classMethod "showsPrec" showCls -- showsPrec+ geSel = classMethod ">=" ordCls -- (>=) — GHC parenthesises with @d >= prec+1@+ showSTy = mkVisFunTyMany stringTy stringTy -- ShowS+ scrut v = Cast (Var v) co+ cons c t = mkCoreConApps consDataCon [Type charTy, c, t] -- c : t+ append s t = mkApps (Var appendId) [Type charTy, s, t] -- s ++ t+ str s = unsafeTcPluginTcM (mkStringExprFS (fsLit s)) -- string literal++ ordIntEv <- newWanted loc (mkClassPred ordCls [intTy])+ let ordIntDict = ctEvExpr ordIntEv++ dId <- freshId intTy "d"+ vId <- freshId wrappedTy "v"++ (alts, fieldWss) <- fmap unzip $ forM dcons \(dc, cosI) -> do+ let realFts = fieldTysAt innerTy dc -- real (bind) types+ modFts = map coercionRKind cosI -- modifier (show-at) types; real when Refl+ name = occNameString (getOccName dc)+ labels = map (occNameString . nameOccName . flSelector) (dataConFieldLabels dc)+ nameStr <- str name+ nameSp <- str (name ++ " ") -- name + the separating space, baked into one literal (as GHC does)+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] realFts+ fieldEvs <- mapM (\ft -> newWanted loc (mkClassPred showCls [ft])) modFts+ rest <- freshId stringTy "r"+ gtBndr <- freshId boolTy "p"+ prec <- conPrec dc++ -- each field shown at its modifier type, with its bound value coerced+ let triples = zip3 modFts fieldEvs (zipWith castInto (map Var xs) cosI)+ spField p (ft, ev, v) =+ mkApps (Var showsPrecSel) [Type ft, ctEvExpr ev, mkUncheckedIntExpr p, v]+ -- prefix: "K " ++ sp 11 x0 (' ' : sp 11 x1 (… t)) — GHC bakes the first+ -- space into the constructor-name literal, then separates the rest.+ goPrefix :: CoreExpr -> CoreExpr+ goPrefix t = case triples of+ [] -> t+ (f0 : more) -> App (spField 11 f0)+ (foldr (\fld acc -> cons (mkCharExpr ' ') (App (spField 11 fld) acc)) t more)+ -- parenthesise the body when @d >= thr+1@ (i.e. @d > thr@), matching the+ -- @showParen (d >= appPrec1) p@ that GHC's stock @deriving@ emits. The+ -- shared continuation @g = \\s -> mk s@ is built once (a single join+ -- point, not duplicated inline); an optional @lead@ literal (the+ -- constructor name) is prepended /outside/ @g@ in each branch, exactly+ -- as GHC floats @showString name@ out of the shared part.+ parenAt :: Integer -> Maybe CoreExpr -> (CoreExpr -> CoreExpr) -> CoreExpr -> TcPluginM CoreExpr+ parenAt thr lead mk t = do+ pId <- freshId showSTy "p"+ sId <- freshId stringTy "s"+ let test :: CoreExpr+ test = mkApps (Var geSel) [Type intTy, ordIntDict, Var dId, mkUncheckedIntExpr (thr + 1)]+ p :: CoreExpr -> CoreExpr -- lead ++ g t' (lead prepended outside the shared g)+ p t' = maybe id append lead (App (Var pId) t')+ pure $ Let (NonRec pId (Lam sId (mk (Var sId)))) $+ Case test gtBndr stringTy+ [ Alt (DataAlt falseDataCon) [] (p t)+ , Alt (DataAlt trueDataCon) []+ (cons (mkCharExpr '(') (p (cons (mkCharExpr ')') t))) ]++ showsBody <-+ if dataConIsInfix dc -- infix: x `op` y at prec+ then do+ opStr <- str (" " ++ name ++ " ")+ let [l, r] = triples+ body t = App (spField (prec + 1) l) (append opStr (App (spField (prec + 1) r) t))+ parenAt prec Nothing body (Var rest)+ else if not (null labels)+ then do -- record: K {l1 = v1, l2 = v2}+ openB <- str " {"; eqB <- str " = "; commaB <- str ", "; closeB <- str "}"+ lblStrs <- mapM str labels+ let recF = zip lblStrs triples+ goRec [(lbl, fld)] c = append lbl (append eqB (App (spField 0 fld) (append closeB c)))+ goRec ((lbl, fld) : more) c =+ append lbl (append eqB (App (spField 0 fld) (append commaB (goRec more c))))+ goRec [] c = append closeB c -- unreachable (record has fields)+ recBody t = append nameStr (append openB (goRec recF t))+ parenAt 10 Nothing recBody (Var rest)+ else if null xs+ then pure (append nameStr (Var rest)) -- nullary: never parenthesised+ else parenAt 10 (Just nameSp) goPrefix (Var rest) -- prefix: share fields, prepend name++ pure (Alt (DataAlt dc) xs (Lam rest showsBody), fieldEvs)++ caseBndr <- freshId innerTy "cb"+ let spImpl = mkLams [dId, vId] (Case (scrut vId) caseBndr showSTy alts)++ -- show x = showsPrec 0 x ""+ -- showList = showList__ (showsPrec 0)+ vShow <- freshId wrappedTy "v"+ vList <- freshId wrappedTy "v"+ let showImpl = Lam vShow (mkApps spImpl [mkUncheckedIntExpr 0, Var vShow, mkNilExpr charTy])+ sp0 = Lam vList (mkApps spImpl [mkUncheckedIntExpr 0, Var vList])+ showListImpl = mkApps (Var showList__Id) [Type wrappedTy, sp0]+ dict = mkClassDict showCls wrappedTy [spImpl, showImpl, showListImpl]+ wanteds = mkNonCanonical ordIntEv+ : map mkNonCanonical (concat fieldWss)+ pure (EvExpr dict, wanteds)++-- | Synthesize a @Bounded@ dictionary. For an enumeration, @minBound@/@maxBound@+-- are the first/last constructors. For a single-constructor product, they are+-- that constructor applied to the field types' own @minBound@/@maxBound@.
+ plugin/Stock/Surface.hs view
@@ -0,0 +1,164 @@+{-# LANGUAGE RankNTypes #-}+-- | Source-level sugar for "Stock.Override": a @parsedResultAction@ that lowers+-- the lowercase, no-backtick surface+--+-- > Override [ x via Sum, Coord at 0 via Sum ] T+--+-- into the honest marker form the type-checker plugin reads+--+-- > Override [ "x" := Sum, At Coord 0 := Sum ] T+--+-- keeping a single infix operator (@:=@); @at@ becomes the prefix marker @At@,+-- and a bare lowercase selector becomes a 'Symbol' literal. The rewrite is+-- /scoped to @Override@ applications/, runs before renaming, and reuses the+-- original sub-trees (so spans survive); @via@\/@at@ elsewhere are untouched.+-- Enabled by the same @-fplugin Stock@ as the solver.+module Stock.Surface (lowerOverrides) where++import GHC.Plugins+import GHC.Hs+import GHC.Types.SourceText (SourceText(NoSourceText))+import Data.Char (isLower)+import Data.Data (Data, gmapT)+import Data.Typeable (Typeable, cast)+import Data.Maybe (fromMaybe)++-- A two-line slice of @syb@ over @base@'s 'Data'/'Typeable' (the GHC AST derives+-- 'Data'), so we depend on no extra package.++-- | Apply @f@ at every subterm, bottom-up. An endofunction on the type of+-- type-preserving polymorphic transformations.+everywhere :: (forall x. Data x => x -> x) -> (forall x. Data x => x -> x)+everywhere f = f . gmapT (everywhere f)++-- | Lift a single-type transformation to act only where the type matches.+mkT :: (Typeable a, Typeable b) => (b -> b) -> a -> a+mkT f = fromMaybe id (cast f)++-- | Rewrite every @Override [ … ]@ config in the parsed module.+lowerOverrides :: ParsedResult -> ParsedResult+lowerOverrides pr =+ pr { parsedResultModule =+ let m = parsedResultModule pr+ in m { hpm_module = everywhere (mkT rewriteTy) (hpm_module m) } }++-- | If this type is @Override T CFG@ (or the @Overriding@\/@Overriding1@\/+-- @Overriding2@ synonyms — all type-first), lower the entries of @CFG@. @CFG@+-- is the /last/ argument here (the wrappers are type-first: @Overriding T cfg@).+rewriteTy :: HsType GhcPs -> HsType GhcPs+rewriteTy ty+ | HsAppTy x f cfg <- ty -- (hd T) cfg+ , L _ (HsAppTy _ hd _) <- f -- f = hd T+ , Just mq <- overrideHeadQual (unLoc hd)+ , Just cfg' <- lowerConfig mq cfg+ = HsAppTy x f cfg' -- keep @hd T@, lower the config+ | otherwise = ty++-- | If this is an @Override@-family head, report /how it was qualified/ — the+-- module alias if written @S.Override@ (@import Stock.Override qualified as S@),+-- or 'Nothing' if unqualified. The generated markers (@:=@, @At@, @Keep@) mirror+-- this, so they resolve no matter how the user imported "Stock.Override".+overrideHeadQual :: HsType GhcPs -> Maybe (Maybe ModuleName)+overrideHeadQual (HsTyVar _ _ (L _ rdr))+ | occNameString (rdrNameOcc rdr) `elem`+ ["Override", "Overriding", "Overriding1", "Overriding2"]+ = Just (fst <$> isQual_maybe rdr)+overrideHeadQual _ = Nothing++-- | Build a marker constructor name (@:=@, @At@, @Keep@), qualified the same way+-- the @Override@ head was, so it is in scope under any import style.+mkMarker :: Maybe ModuleName -> String -> RdrName+mkMarker Nothing nm = mkRdrUnqual (mkTcOcc nm)+mkMarker (Just m) nm = mkRdrQual m (mkTcOcc nm)++-- | Lower a config list by rewriting each element. The config is assumed to be+-- an actual type-level list ('HsExplicitListTy') — i.e. @'[ … ]@, or @[ … ]@+-- under @NoListTuplePuns@. A single-element @[a]@ that parses as the /list+-- type/ is deliberately /not/ reinterpreted (write @'[a]@ instead).+--+-- Two surfaces share this pass: the entry-list form (each element lowered by+-- 'lowerEntry'), and the positional @'[ '[m, _, …] ]@ form whose inner lists+-- carry the @_@ no-op — every type wildcard anywhere in the config is lowered+-- to the @Keep@ marker that the solver reads.+lowerConfig :: Maybe ModuleName -> LHsType GhcPs -> Maybe (LHsType GhcPs)+lowerConfig mq (L l (HsExplicitListTy x p es)) =+ Just (everywhere (mkT (wildToKeep mq)) (L l (HsExplicitListTy x p (map (lowerEntry mq) es))))+lowerConfig _ _ = Nothing++-- | The positional no-op: a type wildcard @_@ ('HsWildCardTy') becomes the+-- @Keep@ marker, qualified to match the @Override@ head. (Bare @Keep@ written by+-- hand is left as-is.)+wildToKeep :: Maybe ModuleName -> HsType GhcPs -> HsType GhcPs+wildToKeep mq (HsWildCardTy _) =+ unLoc (nlHsTyVar NotPromoted (mkMarker mq "Keep"))+wildToKeep _ t = t++-- | Lower one entry. Surfaces:+--+-- * @sel via modifier@ — split the application spine on @via@, rebuild as+-- @(:=) selector modifier@.+-- * @sel via a -> f b@ — @via@ binds looser than @->@: GHC parses this as+-- @(sel via a) -> f b@, so we peel @via@ off the /domain/ and rebuild the+-- modifier as @a -> f b@ (i.e. @sel via (a -> f b)@ without the parens).+-- * @sel := modifier@ — written with the operator directly; lower only the+-- /selector/ (the LHS).+--+-- Anything else is left untouched.+lowerEntry :: Maybe ModuleName -> LHsType GhcPs -> LHsType GhcPs+lowerEntry mq (L l (HsOpTy x prom lhs op rhs))+ | isVarRdr ":=" (unLoc op) =+ L l (HsOpTy x prom (lowerSelector mq (spine lhs)) op rhs)+lowerEntry mq (L l (HsFunTy x arr dom cod))+ | (sel@(_ : _), _via : modAtoms@(_ : _)) <- break (isVar "via") (spine dom) =+ mkPrefix mq ":=" [lowerSelector mq sel, L l (HsFunTy x arr (reassemble modAtoms) cod)]+lowerEntry mq e =+ case break (isVar "via") (spine e) of+ (sel@(_ : _), _via : modAtoms@(_ : _)) ->+ mkPrefix mq ":=" [lowerSelector mq sel, reassemble modAtoms]+ _ -> e++-- | The selector left of @via@: @con at pos@ ⇒ @At con pos@; a bare lowercase+-- head ⇒ a 'Symbol' literal; otherwise reassembled as a type (type-keyed).+lowerSelector :: Maybe ModuleName -> [LHsType GhcPs] -> LHsType GhcPs+lowerSelector mq atoms =+ case break (isVar "at") atoms of+ (con@(_ : _), _at : pos@(_ : _)) ->+ mkPrefix mq "At" [nameOrType con, reassemble pos]+ _ -> nameOrType atoms++-- | A single bare lowercase variable ⇒ field-name 'Symbol' literal; else a type.+nameOrType :: [LHsType GhcPs] -> LHsType GhcPs+nameOrType [L l (HsTyVar _ NotPromoted (L _ rdr))]+ | isLowerName rdr =+ L l (HsTyLit noExtField (HsStrTy NoSourceText (occNameFS (rdrNameOcc rdr))))+nameOrType atoms = reassemble atoms++-- ----- application-spine helpers -------------------------------------------++-- | Flatten a left-nested @HsAppTy@ into its atoms (head first).+spine :: LHsType GhcPs -> [LHsType GhcPs]+spine (L _ (HsAppTy _ f a)) = spine f ++ [a]+spine t = [t]++-- | Re-nest a non-empty atom list into a left-associated application.+reassemble :: [LHsType GhcPs] -> LHsType GhcPs+reassemble = foldl1 mkHsAppTy++-- | Prefix application of a marker type constructor named @nm@ to @args@,+-- qualified to match the @Override@ head (see 'mkMarker').+mkPrefix :: Maybe ModuleName -> String -> [LHsType GhcPs] -> LHsType GhcPs+mkPrefix mq nm = foldl mkHsAppTy (nlHsTyVar NotPromoted (mkMarker mq nm))++-- ----- predicates ----------------------------------------------------------++isVar :: String -> LHsType GhcPs -> Bool+isVar nm (L _ (HsTyVar _ _ (L _ rdr))) = isVarRdr nm rdr+isVar _ _ = False++isVarRdr :: String -> RdrName -> Bool+isVarRdr nm rdr = occNameString (rdrNameOcc rdr) == nm++isLowerName :: RdrName -> Bool+isLowerName rdr = case occNameString (rdrNameOcc rdr) of+ (c : _) -> isLower c+ _ -> False
+ plugin/Stock/TestEquality.hs view
@@ -0,0 +1,122 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+-- | A deliberately /minimal, forward-safe/ 'Data.Type.Equality.TestEquality'+-- (and 'Data.Type.Coercion.TestCoercion') synthesizer.+--+-- It handles exactly the unambiguous "finite singleton" GADT: a one-parameter+-- type whose every constructor is nullary, has no existentials, and pins the+-- parameter to a /ground/ type:+--+-- > data T a where { TInt :: T Int; TBool :: T Bool }+--+-- For these the lawful behaviour is forced: @testEquality x y@ is @Just Refl@+-- exactly when the type /indices/ of @x@ and @y@ coincide (NOT when they are+-- the same constructor: two constructors pinning the same type are equal), and+-- @Nothing@ otherwise. Because that is the only law-abiding implementation, it+-- can never disagree with a future, more general design, so it commits us to+-- nothing. Anything outside the subset is refused.+module Stock.TestEquality (synthTestEquality, synthTestCoercion) where++import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin (TcPluginM, unsafeTcPluginTcM)+import GHC.Tc.Types.Constraint (Ct)+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence (EvTerm(EvExpr))+import GHC.Core.Class (Class, classMethods)+import GHC.Core.TyCo.Compare (eqType)+import Stock.Internal++-- | A datacon's GADT equality refinements (no public accessor; via the sig).+dcEqSpec :: DataCon -> [EqSpec]+dcEqSpec dc = let (_, _, eqs, _, _, _) = dataConFullSig dc in eqs++-- | A constructor in the supported subset; returns its pinned ground index.+pinnedGround :: DataCon -> Maybe Type+pinnedGround dc = case dcEqSpec dc of+ [es] | null (dataConExTyCoVars dc) -- no existentials+ , null (dataConOrigArgTys dc) -- nullary (no value fields)+ , let ty = snd (eqSpecPair es)+ , isEmptyVarSet (tyCoVarsOfType ty) -- ground (closed) index+ -> Just ty+ _ -> Nothing++synthTestEquality, synthTestCoercion+ :: GenEnv -> Class -> CtLoc -> Type -> Type -> TcPluginM (Maybe (EvTerm, [Ct]))+synthTestEquality = synthEqLike True+synthTestCoercion = synthEqLike False++-- | @useRefl = True@ ⇒ 'TestEquality' (@(:~:)@ \/ @Refl@); @False@ ⇒+-- 'TestCoercion' (@Coercion@).+synthEqLike :: Bool -> GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthEqLike useRefl gen cls _loc wrappedTy f =+ case (geStock1 gen, tyConAppTyCon_maybe f) of+ (Just st1Tc, Just fTc)+ | null (tyConAppArgs f) -- F is a bare one-param tycon+ , dcons@(dc0 : _) <- tyConDataCons fTc+ , Just pins <- traverse pinnedGround dcons+ , (es0 : _) <- dcEqSpec dc0+ -- the witness type (@(:~:)@ \/ @Coercion@) straight from the method's+ -- signature, so we never have to name a (re-exported) module.+ , (meth : _) <- classMethods cls+ , (witTc : _) <- [ tc | tc <- nonDetEltsUniqSet (tyConsOfType (varType meth))+ , nameOccName (tyConName tc)+ == mkTcOcc (if useRefl then ":~:" else "Coercion") ] -> do+ let witCon = tyConSingleDataCon witTc+ kK = tyVarKind (fst (eqSpecPair es0))+ coAt = coDown1 gen st1Tc wrappedTy f f+ aTv <- freshTyVarK kK "a"+ bTv <- freshTyVarK kK "b"+ xId <- freshId (mkAppTy wrappedTy (mkTyVarTy aTv)) "x"+ yId <- freshId (mkAppTy wrappedTy (mkTyVarTy bTv)) "y"+ wbX <- freshId (mkTyConApp fTc [mkTyVarTy aTv]) "wx"+ wbY <- freshId (mkTyConApp fTc [mkTyVarTy bTv]) "wy"+ let aTy = mkTyVarTy aTv ; bTy = mkTyVarTy bTv+ witOf x y = mkTyConApp witTc [kK, x, y]+ resTy = mkTyConApp maybeTyCon [witOf aTy bTy]+ nothingE = mkCoreConApps nothingDataCon [Type (witOf aTy bTy)]+ -- same index: cox : a~#t, coy : b~#t ⇒ abCo : a~#b.+ -- Refl :: forall k (a b). (b ~# a) => a :~: b (eqSpec)+ -- Coercion :: forall k (a b). Coercible a b => Coercion a b+ -- so we feed the proof directly; for Coercion we first box the+ -- representational coercion into a Coercible dictionary with the+ -- wired-in 'coercibleDataCon' (@MkCoercible :: (a ~R# b) ->+ -- Coercible a b@). No Cast, no constraint solving.+ same cox coy =+ let abCo = mkTransCo (mkCoVarCo cox) (mkSymCo (mkCoVarCo coy))+ proof | useRefl = Coercion (mkSymCo abCo) -- b ~# a (nominal)+ | otherwise = mkCoreConApps coercibleDataCon+ [Type kK, Type aTy, Type bTy+ , Coercion (mkSubCo abCo)] -- a ~R# b boxed+ wit = mkCoreConApps witCon [Type kK, Type aTy, Type bTy, proof]+ in mkCoreConApps justDataCon [Type (witOf aTy bTy), wit]+ -- testEquality compares the type /indices/, not constructor tags:+ -- two constructors pinning the same ground type ⇒ Just Refl.+ let innerAlts ti cox = mapM mkInner (zip dcons pins)+ where mkInner (dcj, tj) = do+ coy <- freshCoVar (mkPrimEqPred bTy tj)+ let rhs = if eqType ti tj then same cox coy else nothingE+ pure (Alt (DataAlt dcj) [coy] rhs)+ outerAlts <- mapM+ (\(dci, ti) -> do+ cox <- freshCoVar (mkPrimEqPred aTy ti)+ ialts <- innerAlts ti cox+ let inner = Case (Cast (Var yId) (coAt bTy)) wbY resTy ialts+ pure (Alt (DataAlt dci) [cox] inner))+ (zip dcons pins)+ let impl = mkCoreLams [aTv, bTv, xId, yId] $+ Case (Cast (Var xId) (coAt aTy)) wbX resTy outerAlts+ -- TestEquality/TestCoercion are poly-kinded (@class C (f :: k ->+ -- Type)@), so the dictionary takes the kind @k@ first.+ dict = mkCoreConApps (classDataCon cls) [Type kK, Type wrappedTy, impl]+ pure (Just (EvExpr dict, []))+ _ -> pure Nothing++freshCoVar :: Type -> TcPluginM CoVar+freshCoVar ty = do+ u <- unsafeTcPluginTcM getUniqueM+ pure (mkCoVar (mkSystemName u (mkVarOccFS (fsLit "co"))) ty)
+ plugin/Stock/Trans.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE InstanceSigs #-}+{-# LANGUAGE BlockArguments #-}+-- | A two-screen slice of @transformers@: the three monad transformers the+-- plugin uses (@ReaderT@, strict @WriterT@, @MaybeT@), inlined so the library+-- depends only on @base@ and @ghc@. They are used /only/ as @DerivingVia@+-- targets (the synthesis monad in "Stock.Derive", the first-success 'Monoid' in+-- "Stock.Internal"), so the representations match @transformers@ exactly — that+-- is what lets the @via@ coercions go through — and none of the combinators+-- (@lift@, @ask@, @tell@, @runReaderT@, …) are needed beyond the constructors.+module Stock.Trans+ ( ReaderT(..)+ , WriterT(..)+ , MaybeT(..)+ ) where++import Control.Applicative (Alternative(..))+import Control.Monad (ap)+-- liftA2 comes from Prelude (base >= 4.18 / GHC >= 9.6)++-- | @r -> m a@, exactly as in @Control.Monad.Trans.Reader@.+newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }++-- | @m (a, w)@ — the /strict/ writer (value first), as in+-- @Control.Monad.Trans.Writer.Strict@.+newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }++-- | @m (Maybe a)@, exactly as in @Control.Monad.Trans.Maybe@.+newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) }++instance Functor m => Functor (ReaderT r m) where+ fmap :: (a -> b) -> ReaderT r m a -> ReaderT r m b+ fmap f (ReaderT g) = ReaderT (fmap f . g)++instance Applicative m => Applicative (ReaderT r m) where+ pure :: a -> ReaderT r m a+ pure = ReaderT . const . pure+ (<*>) :: ReaderT r m (a -> b) -> ReaderT r m a -> ReaderT r m b+ ReaderT f <*> ReaderT x = ReaderT \r -> f r <*> x r++instance Monad m => Monad (ReaderT r m) where+ (>>=) :: ReaderT r m a -> (a -> ReaderT r m b) -> ReaderT r m b+ ReaderT x >>= k = ReaderT \r -> x r >>= \a -> runReaderT (k a) r++instance Functor m => Functor (WriterT w m) where+ fmap :: (a -> b) -> WriterT w m a -> WriterT w m b+ fmap f (WriterT m) = WriterT (fmap (\(a, w) -> (f a, w)) m)++instance (Monoid w, Applicative m) => Applicative (WriterT w m) where+ pure :: a -> WriterT w m a+ pure a = WriterT (pure (a, mempty))+ (<*>) :: WriterT w m (a -> b) -> WriterT w m a -> WriterT w m b+ WriterT mf <*> WriterT mx = WriterT (liftA2 k mf mx)+ where k (f, w) (x, w') = (f x, w <> w')++instance (Monoid w, Monad m) => Monad (WriterT w m) where+ (>>=) :: WriterT w m a -> (a -> WriterT w m b) -> WriterT w m b+ WriterT m >>= k = WriterT do+ (a, w) <- m+ (b, w') <- runWriterT (k a)+ pure (b, w <> w')++instance Functor m => Functor (MaybeT m) where+ fmap :: (a -> b) -> MaybeT m a -> MaybeT m b+ fmap f (MaybeT m) = MaybeT (fmap (fmap f) m)++instance Monad m => Applicative (MaybeT m) where+ pure :: a -> MaybeT m a+ pure = MaybeT . pure . Just+ (<*>) :: MaybeT m (a -> b) -> MaybeT m a -> MaybeT m b+ (<*>) = ap++instance Monad m => Monad (MaybeT m) where+ (>>=) :: MaybeT m a -> (a -> MaybeT m b) -> MaybeT m b+ MaybeT m >>= k = MaybeT do+ ma <- m+ case ma of+ Nothing -> pure Nothing+ Just a -> runMaybeT (k a)++instance Monad m => Alternative (MaybeT m) where+ empty :: MaybeT m a+ empty = MaybeT (pure Nothing)+ (<|>) :: MaybeT m a -> MaybeT m a -> MaybeT m a+ MaybeT a <|> MaybeT b = MaybeT do+ ma <- a+ case ma of+ Nothing -> b+ Just _ -> pure ma
+ plugin/Stock/Traversable.hs view
@@ -0,0 +1,166 @@+{-# LANGUAGE CPP #-}+{-# LANGUAGE BlockArguments #-}+{-# LANGUAGE LambdaCase #-}+{-# OPTIONS_GHC -Wno-x-partial -Wno-incomplete-uni-patterns -Wno-unused-imports #-}+-- | @Traversable (Stock1 F)@, synthesized directly (DeriveTraversable-style+-- Core), NOT by coercion. @traverse@'s result @f (t b)@ places the wrapper+-- under an /abstract/ applicative @f@ (nominal role), so DerivingVia cannot+-- coerce @Traversable (Stock1 F)@ onto @F@ — but the instance itself is+-- perfectly definable and usable at the wrapper. Put it on your own type with+-- the one-liner (which works with @Override1@ too):+--+-- > instance Traversable F where+-- > traverse g = fmap unStock1 . traverse g . Stock1+module Stock.Traversable (synthTraversable) where++import GHC.Plugins hiding (TcPlugin)+import GHC.Tc.Plugin+import GHC.Tc.Types.Constraint+#if MIN_VERSION_ghc(9,12,0)+import GHC.Tc.Types.CtLoc (CtLoc)+#else+import GHC.Tc.Types.Constraint (CtLoc)+#endif+import GHC.Tc.Types.Evidence+import GHC.Core.Class (Class)+import GHC.Core.Predicate (mkClassPred)+import GHC.Core.Multiplicity (scaledThing)+import GHC.Core.TyCo.Compare (eqType)+import GHC.Core.TyCo.Subst (substTyWith)+import GHC.Core.TyCo.Rep (UnivCoProvenance(PluginProv))+import GHC.Builtin.Names (applicativeClassName, functorClassName, foldableClassName)+import Control.Monad (forM, zipWithM)+import Data.List (zipWith4)+import Stock.Derive (classMethod)+import Stock.Internal+import Stock.Functor (synthFunctor, synthFoldable)++-- | Synthesize @Traversable (Stock1 F)@: per constructor, @pure mkCon \<*\> f1+-- \<*\> … \<*\> fn@ where the parameter field uses the supplied @g@, a constant+-- uses @pure@, and a sub-functor @H a@ field uses @traverse \@H g@ (an+-- @Override1@-reshaped functor traverses through the modifier, re-wrapped with+-- @pure coerce \<*\> _@ — never a cast under the abstract @f@). @Functor@ and+-- @Foldable@ superclasses come from their own synthesizers.+synthTraversable :: GenEnv -> Class -> CtLoc -> Type -> Type+ -> TcPluginM (Maybe (EvTerm, [Ct]))+synthTraversable gen travCls loc wrappedTy f =+ case geStock1 gen of+ Just st1Tc+ | let (realF, mMods) = peelOverride1 gen f+ , Just fTc <- tyConAppTyCon_maybe realF -> do+ appCls <- tcLookupClass applicativeClassName+ funcCls <- tcLookupClass functorClassName+ foldCls <- tcLookupClass foldableClassName+ let fixed = tyConAppArgs realF+ dcons = tyConDataCons fTc+ traverseSel = classMethod "traverse" travCls+ pureSel = classMethod "pure" appCls+ apSel = classMethod "<*>" appCls+ coAt t = coDown1 gen st1Tc wrappedTy f realF t -- Stock1 (Override1? F) t ~R F t+ fTv <- freshTyVarK (mkVisFunTyMany liftedTypeKind liftedTypeKind) "f" -- f :: Type -> Type+ aTv <- freshTyVar "a" ; bTv <- freshTyVar "b"+ let fTy = mkTyVarTy fTv ; aTy = mkTyVarTy aTv ; bTy = mkTyVarTy bTv+ fOf t = mkAppTy fTy t+ innerA = mkTyConApp fTc (fixed ++ [aTy])+ gTy = mkVisFunTyMany aTy (fOf bTy) -- a -> f b+ stbTy = mkAppTy wrappedTy bTy -- Stock1 F b+ dApp <- freshId (mkClassPred appCls [fTy]) "dApp"+ gId <- freshId gTy "g"+ xId <- freshId (mkAppTy wrappedTy aTy) "x"+ cb <- freshId innerA "cb"+ let pureE ty e = mkApps (Var pureSel) [Type fTy, Var dApp, Type ty, e]+ apE tyA tyB ac fe = mkApps (Var apSel) [Type fTy, Var dApp, Type tyA, Type tyB, ac, fe]+ subB t = substTyWith [aTv] [bTy] t -- t[a := b]+ -- GHC's @ft_*@ traversal of a field: a constant ⇒ @pure x@; the+ -- parameter ⇒ @g x@; a tuple ⇒ @pure (,..) \<*\> t1 \<*\> …@ (every+ -- component); a covariant @H larg@ ⇒ @traverse \@H@ (nested @[[a]]@ ⇒+ -- @traverse (traverse g)@); a function field rejected. Result is+ -- @f (subB ft)@.+ traverseField ft xe+ | not (aTv `elemVarSet` tyCoVarsOfType ft) = pure (Just (pureE ft xe, []))+ | ft `eqType` aTy = pure (Just (App (Var gId) xe, []))+ | Just _ <- splitFunTy_maybe ft = pure Nothing+ | Just (tc, args) <- splitTyConApp_maybe ft+ , isTupleTyCon tc, length args >= 2 = do+ xs <- mapM (`freshId` "u") args+ rs <- zipWithM traverseField args (map Var xs)+ case sequence rs of+ Nothing -> pure Nothing+ Just travs -> do+ let subArgs = map subB args+ dc = tupleDataCon Boxed (length args)+ subTup = subB ft+ rs' = scanr mkVisFunTyMany subTup subArgs+ ys <- mapM (`freshId` "v") subArgs+ cb <- freshId ft "cb"+ let mkTup = mkLams ys (mkCoreConApps dc (map Type subArgs ++ map Var ys))+ built = foldl (\ac (k, te, sa) -> apE sa (rs' !! (k + 1)) ac te)+ (pureE (head rs') mkTup)+ (zip3 [0 :: Int ..] (map fst travs) subArgs)+ pure (Just ( Case xe cb (fOf subTup) [Alt (DataAlt dc) xs built]+ , concatMap snd travs ))+ | Just (h, larg) <- splitAppTy_maybe ft+ , not (aTv `elemVarSet` tyCoVarsOfType h) =+ if larg `eqType` aTy+ then do ev <- newWanted loc (mkClassPred travCls [h])+ pure (Just ( mkApps (Var traverseSel)+ [Type h, ctEvExpr ev, Type fTy, Type aTy, Type bTy, Var dApp, Var gId, xe]+ , [mkNonCanonical ev] ))+ else do y <- freshId larg "y"+ inner <- traverseField larg (Var y)+ case inner of+ Nothing -> pure Nothing+ Just (e, w) -> do+ ev <- newWanted loc (mkClassPred travCls [h])+ pure (Just ( mkApps (Var traverseSel)+ [Type h, ctEvExpr ev, Type fTy, Type larg, Type (subB larg)+ , Var dApp, Lam y e, xe]+ , mkNonCanonical ev : w ))+ | otherwise = pure Nothing+ -- one field's effect @f rvFt@; Override1 reshapes the (one-level)+ -- functor @h a -> m a@, otherwise the full structural walk.+ fieldOf i x ftA rvFt = case override1Mod gen mMods i of+ Just m -> do -- Override1: traverse through @m@, re-wrap @m b -> h b@+ ev <- newWanted loc (mkClassPred travCls [m])+ let coS = mkStockCo (PluginProv "stock") Representational ftA (mkAppTy m aTy)+ coRb = mkStockCo (PluginProv "stock") Representational (mkAppTy m bTy) rvFt+ trav = mkApps (Var traverseSel)+ [Type m, ctEvExpr ev, Type fTy, Type aTy, Type bTy+ , Var dApp, Var gId, Cast (Var x) coS] -- :: f (m b)+ mb <- freshId (mkAppTy m bTy) "mb"+ let coerceFn = Lam mb (Cast (Var mb) coRb) -- m b -> h b+ pure (Just ( apE (mkAppTy m bTy) rvFt+ (pureE (mkVisFunTyMany (mkAppTy m bTy) rvFt) coerceFn) trav+ , [mkNonCanonical ev] ))+ Nothing -> traverseField ftA (Var x)+ malts <- forM dcons \dc -> do+ let fts = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [aTy]))+ rvFts = map scaledThing (dataConInstOrigArgTys dc (fixed ++ [bTy]))+ xs <- zipWithM (\n ft -> freshId ft ("x" ++ show n)) [0 :: Int ..] fts+ mfes <- sequence (zipWith4 fieldOf [0 :: Int ..] xs fts rvFts)+ case sequence mfes of+ Nothing -> pure Nothing+ Just fes -> do+ let (fieldExprs, wss) = unzip fes+ ys <- zipWithM (\n ft -> freshId ft ("y" ++ show n)) [0 :: Int ..] rvFts+ let mkCon = mkLams ys (Cast (mkCoreConApps dc (map Type (fixed ++ [bTy]) ++ map Var ys))+ (mkSymCo (coAt bTy))) -- rvFt.. -> Stock1 F b+ rs = scanr mkVisFunTyMany stbTy rvFts -- R_0 … R_n(=Stock1 F b)+ body = foldl (\ac (k, fe, rvFt) -> apE rvFt (rs !! (k + 1)) ac fe)+ (pureE (head rs) mkCon)+ (zip3 [0 :: Int ..] fieldExprs rvFts)+ pure (Just (Alt (DataAlt dc) xs body, concat wss))+ case sequence malts of+ Nothing -> pure Nothing+ Just altWss -> do+ let (alts, wss) = unzip altWss+ traverseImpl = mkLams [fTv, aTv, bTv, dApp, gId, xId]+ (destructInner fTc (fixed ++ [aTy]) (Cast (Var xId) (coAt aTy)) cb (fOf stbTy) alts)+ mFunc <- synthFunctor gen funcCls loc wrappedTy f+ mFold <- synthFoldable gen foldCls loc wrappedTy f+ case (mFunc, mFold) of+ (Just (fEv, fWs), Just (foEv, foWs)) -> do+ dict <- recDictWith travCls wrappedTy [unwrapEv fEv, unwrapEv foEv] [(0, traverseImpl)]+ pure (Just (EvExpr dict, fWs ++ foWs ++ concat wss))+ _ -> pure Nothing+ _ -> pure Nothing
+ src/Stock/Type.hs view
@@ -0,0 +1,34 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE KindSignatures #-}+{-# LANGUAGE PolyKinds #-}+{-# LANGUAGE StandaloneKindSignatures #-}++-- | Newtype wrappers that drive the Stock plugin. Writing+-- @deriving C via Stock T@ (or @via Stock1 F@ for a class over a type+-- constructor) asks the plugin to synthesize the instance from @T@'s+-- structure — no @Generic@, no hand-written instances.+module Stock.Type+ ( Stock(Stock, unStock)+ , Stock1(Stock1, unStock1)+ , Stock2(Stock2, unStock2)+ ) where++import Data.Kind (Type)++-- | Wrap a type @a@ so that @deriving C via Stock a@ synthesizes @C a@.+type Stock :: Type -> Type+newtype Stock a = Stock { unStock :: a }++-- | Wrap a type constructor @f@ so that @deriving C via Stock1 f@ synthesizes+-- a @C f@ instance (for classes over type constructors, e.g. @Functor@).+-- Poly-kinded in the index (@f :: k -> Type@) so it works for classes over+-- non-@Type@ indices too (e.g. @TestEquality@) — maximally polymorphic.+type Stock1 :: forall k. (k -> Type) -> (k -> Type)+newtype Stock1 f a = Stock1 { unStock1 :: f a }++-- | Wrap a two-parameter type constructor @p@ so that @deriving C via Stock2 p@+-- synthesizes a @C p@ instance (for classes over two-parameter type+-- constructors, e.g. @Bifunctor@, @Bifoldable@, @Eq2@, @Ord2@, @Show2@,+-- @Read2@, @Category@).+type Stock2 :: forall k j. (k -> j -> Type) -> (k -> j -> Type)+newtype Stock2 bi a b = Stock2 { unStock2 :: bi a b }
+ stock.cabal view
@@ -0,0 +1,196 @@+cabal-version: 3.0+name: stock+version: 0.1.0.0+synopsis: Stock-style deriving via coercion, with no Generic+description:+ A GHC type-checker plugin that derives class instances for @Stock T@+ and higher-kinded variants at /compile time/, straight from the+ structure of /T/ without a @Generic@ representation or runtime cost.+ .+ Supported classes:+ .+ * @Stock@: Eq, Ord, Show, Read, Semigroup, Monoid, Enum, Bounded, Ix, Generic+ * @Stock1@: Functor, Foldable, Traversable, Contravariant, Applicative, Eq1, Ord1, Show1, Read1, Generic1, TestEquality, TestCoercion+ * @Stock2@: Bifunctor, Bifoldable, Bitraversable, Eq2, Ord2, Show2, Read2, Category+ .+ Every claim below is machine-checked with @inspection-testing@ (it+ compares optimised Core, not behaviour):+ .+ * For @Eq@, @Ord@, @Enum@, @Functor@, @Bounded@ and @Foldable@ the+ emitted Core is /byte-identical/ to GHC's own stock deriving.+ * @Traversable@ and @Bitraversable@ — which GHC cannot stock-derive+ at all — produce a @traverse@\/@bitraverse@ /byte-identical/ to the+ natural hand-written definition.+ * Every remaining class is proven to erase the @Stock@ wrapper and+ its coercions /completely/, so the instance is exactly as fast as a+ hand-written one and behaves identically to stock deriving wherever+ GHC has it.+ .+ In short: where GHC derives the class, the result is the same Core+ GHC emits; where it does not, the result is the Core you would have+ written by hand.+ .+ @Traversable@\/@Bitraversable@ are synthesised as genuine instances+ but cannot be reached by a bare @deriving via@ (the coercion is+ blocked by the abstract applicative's nominal role; see @"Stock"@).+ .+ Companion packages add more classes through @DeriveStock@ instances+ (see @"Stock.Derive"@), discovered automatically without an extra+ @-fplugin@ flag :+ .+ * @stock-deepseq@: NFData, NFData1, NFData2+ * @stock-hashable@: Hashable, Hashable1, Hashable2+ * @stock-aeson@: ToJSON, ToJSON1, ToJSON2; FromJSON, FromJSON1, FromJSON2+ * @stock-quickcheck@: Arbitrary, Arbitrary1, Arbitrary2; CoArbitrary+ * @stock-profunctors@: Profunctor+ .+ Ordinary @DerivingVia@ modifiers compose with @Stock@:+ @Down (Stock T)@ reverses ordering, enumeration and bounds;+ @Backwards (Stock1 F)@ reverses @Applicative@ effects; @Reverse+ (Stock1 F)@ reverses @Foldable@\/@Traversable@.+ .+ > {-# options_ghc -fplugin Stock #-}+ > {-# language DerivingVia #-}+ > + > import Stock+ > import Data.Ord (Down(..))+ >+ > -- >>> sort [Bronze, Silver, Gold]+ > -- [Gold,Silver,Bronze]+ > data Place = Bronze | Silver | Gold+ > deriving (Eq, Show) via Stock Place+ > deriving (Ord, Bounded, Enum) via Down (Stock Place)+ .+ Per-field modifiers (@Override@, @"Stock.Override"@, re-exported by+ @"Stock"@) customise individual fields by name, type, or position; @_@+ leaves a field unchanged. A modifier is any newtype with the relevant+ instance.+ . + Hit points and coins accumulate with addition, poisoning+ contaminates by disjunction (or), @items@, and @weapons@ union with+ addition to product a multiset.+ .+ > import Data.Map.Monoidal (MonoidalMap(..))+ > ..+ > type MultiSet key = MonoidalMap key (Sum Int)+ > + > data Inventory = Inventory+ > { hp :: Int+ > , coins :: Int+ > , poisoned :: Bool+ > , items :: Map Item Int -- these unfortunately default+ > , weapons :: Map Weapon Int -- to left-biased union+ > }+ > deriving (Eq, Ord, Show, Read) via+ > Stock Inventory+ > deriving (Semigroup, Monoid) via + > Overriding Inventory+ > '[ hp via Sum + > , coins via Sum + > , poisoned via Any+ > , items via MultiSet Item+ > , weapons via MultiSet Weapon+ > ]+ .+ Synthesis runs once per instance (not per use): @deriving Cls via Stock+ T@ produces a single shared @instance Cls T@ that every call reuses.+license: BSD-3-Clause+license-file: LICENSE+author: Baldur Blöndal+maintainer: baldur.blondal@iohk.io+category: Type System+build-type: Simple+tested-with: GHC >= 9.6 && < 9.15+ , GHC == 9.6.7+ , GHC == 9.8.1, GHC == 9.8.2, GHC == 9.8.4+ , GHC == 9.10.1, GHC == 9.10.2, GHC == 9.10.3+ , GHC == 9.12.1, GHC == 9.12.2, GHC == 9.12.4+ , GHC == 9.14.1+extra-doc-files: README.md+ CHANGELOG.md+extra-source-files: LICENSE++common warnings+ ghc-options: -Wall -Wcompat -Wincomplete-record-updates+ -Wincomplete-uni-patterns -Wredundant-constraints+ default-language: GHC2021++library+ import: warnings+ exposed-modules: Stock+ Stock.Type+ Stock.Derive+ Stock.Override+ Stock.Surface+ Stock.Internal+ Stock.Compat+ Stock.Bounded+ Stock.Eq+ Stock.Ord+ Stock.Semigroup+ Stock.Show+ Stock.Read+ Stock.Enum+ Stock.Functor+ Stock.Applicative+ Stock.Traversable+ Stock.TestEquality+ Stock.Bifunctor+ Stock.Generic+ Stock.Classes1+ other-modules: Stock.Trans+ build-depends: base >=4.18 && <5,+ ghc >=9.6 && <9.16+ hs-source-dirs: src plugin++test-suite examples+ import: warnings+ type: exitcode-stdio-1.0+ main-is: Main.hs+ other-modules: QualOverride+ build-depends: base >=4.18 && <5,+ transformers < 0.7,+ stock+ ghc-options: -fplugin=Stock+ hs-source-dirs: examples++test-suite spec+ import: warnings+ type: exitcode-stdio-1.0+ main-is: Spec.hs+ other-modules: Twin+ build-depends: base >=4.18 && <5,+ stock+ ghc-options: -fplugin=Stock+ hs-source-dirs: test++benchmark bench+ import: warnings+ type: exitcode-stdio-1.0+ main-is: Bench.hs+ build-depends: base >=4.18 && <5,+ stock+ ghc-options: -O2 -rtsopts "-with-rtsopts=-K512m" -fplugin=Stock+ hs-source-dirs: bench++benchmark configs+ import: warnings+ type: exitcode-stdio-1.0+ main-is: Configs.hs+ build-depends: base, stock+ ghc-options: -O2 -fplugin=Stock+ hs-source-dirs: bench++test-suite inspection+ type: exitcode-stdio-1.0+ main-is: Inspection.hs+ build-depends: base, stock, inspection-testing+ ghc-options: -fplugin=Stock+ hs-source-dirs: inspection+ default-language: GHC2021+ if impl(ghc >= 9.14)+ buildable: False++source-repository head+ type: git+ location: https://github.com/Icelandjack/stock.git
+ test/Spec.hs view
@@ -0,0 +1,1076 @@+{-# LANGUAGE DerivingVia #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE GADTs #-}+{-# LANGUAGE StandaloneDeriving #-}+{-# LANGUAGE UndecidableInstances #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE TypeOperators #-}+{-# LANGUAGE TypeFamilies #-}+-- numeric literals default to Integer in a couple of checks, and the twin+-- types intentionally have unused selectors — both fine in a test module.+{-# OPTIONS_GHC -fplugin Stock -Wno-type-defaults -Wno-unused-top-binds #-}++-- | Test-suite for the Virtual-Via plugin. Each synthesized instance is+-- checked against the corresponding @deriving@-derived "twin" type (so the+-- oracle is GHC's own stock deriving), plus round-trip properties.+module Main (main) where++import qualified Stock+import qualified Twin+import Stock.Override (Override(..), Overriding, Override1(..), Overriding1, Override2(..), Overriding2, type (:=), type (-->), At, Keep)+import Control.Applicative (ZipList(..))+import Data.Ord (Down(..))+import qualified Data.Monoid as Mon (Sum(..), Product(..))+import Data.Ix (Ix, range, index, inRange, rangeSize)+import Data.Functor.Contravariant (Contravariant(..), Predicate(..))+import Data.Functor.Classes (Eq1(..), Ord1(..), Show1(..), showsPrec1, Read1(..), readsPrec1, Eq2(..), Ord2(..), Show2(..), Read2(..))+import Text.Read (readPrec, readListPrec, readPrec_to_S)+import Data.Functor.Identity (Identity(..))+import Data.Bifunctor (Bifunctor(..))+import Data.Bifoldable (Bifoldable(..))+import Data.Bitraversable (Bitraversable(..))+import qualified Data.Foldable+import GHC.Generics+ ( Generic, Generically(..), Generically1(..), Rep, from, to, M1(..)+ , datatypeName, conName, conIsRecord, conFixity, selDecidedStrictness+ , Fixity(..), Associativity(..), DecidedStrictness(..), D1, Meta(..)+ , Generic1, from1, to1 )+import qualified GHC.Generics as G+import Data.Kind (Type)+import Data.Coerce (coerce)+import Control.Category (Category)+import qualified Control.Category as Cat+import Control.Arrow (Kleisli(..))+import Data.Type.Equality ((:~:)(Refl), TestEquality(..), castWith)+import Data.Type.Coercion (TestCoercion(..), coerceWith)+import System.Exit (exitFailure)+import Data.List (isInfixOf)+import qualified Data.List+import Control.Monad (unless)+import Control.Exception (try, evaluate, SomeException)++-- ----- types under test (instances synthesized by the plugin) -------------++data Color = Red | Green | Blue+ deriving (Eq, Ord, Show, Read, Enum, Bounded, Ix) via Stock.Stock Color++-- Bounded for a single-constructor product: each field takes its own bound.+data BB = BB Bool Ordering+ deriving (Eq, Show, Bounded) via Stock.Stock BB++-- A "finite singleton" GADT: TestEquality/TestCoercion via Stock1.+data TY a where+ TInt :: TY Int+ TBool :: TY Bool+ TChar :: TY Char+deriving via Stock.Stock1 TY instance TestEquality TY+deriving via Stock.Stock1 TY instance TestCoercion TY++-- Two constructors share an index (both @TZ_ :: TZ Int@): testEquality compares+-- the type index, not the tag, so TZa\/TZb are mutually "equal".+data TZ a where+ TZa :: TZ Int+ TZb :: TZ Int+ TZc :: TZ Bool+deriving via Stock.Stock1 TZ instance TestEquality TZ++data Sum = A | B Int | C Int Bool | Rec { rf :: Int, rg :: Bool }+ deriving (Eq, Ord, Show, Read) via Stock.Stock Sum+ deriving Generic via Stock.Stock Sum++data Pair a = Pair a a+ deriving (Eq, Ord, Show, Read) via Stock.Stock (Pair a)++-- infix constructors with distinct fixities+infixr 5 :+:+infixl 6 :*:+data Expr = Lit Int | Expr :+: Expr | Expr :*: Expr+ deriving (Eq, Show, Read) via Stock.Stock Expr++infixr 5 :+.+infixl 6 :*.+data Expr' = Lit' Int | Expr' :+. Expr' | Expr' :*. Expr'+ deriving (Eq, Show)++data Prod = Prod [Int] [Int]+ deriving (Eq, Show) via Stock.Stock Prod+ deriving Generic via Stock.Stock Prod+ deriving (Semigroup, Monoid) via Generically Prod++-- direct pointwise Semigroup/Monoid (a "faster Generically"), same result+data Sg = Sg [Int] [Int]+ deriving (Eq, Show)+ deriving (Semigroup, Monoid) via Stock.Stock Sg++-- per-field Override: combine cx additively (Sum) and cy multiplicatively+-- (Product) — both unsaturated @Type -> Type@ modifiers, broadcast to the+-- field's own type. A behaviour you cannot get from plain @Stock Coord@+-- without rewriting the datatype's field types.+data Coord = Coord { cx :: Int, cy :: Int }+ deriving (Eq, Show)+ deriving Semigroup+ -- lowercase surface sugar (lowered to "cx" := Sum, "cy" := Product by the+ -- same -fplugin Stock at parse time):+ via Stock.Stock (Override Coord [ cx via Mon.Sum, cy via Mon.Product ])++-- type-keyed Override: every Int field via Sum (no record labels needed)+data TK = TK Int Int+ deriving (Eq, Show)+ deriving Semigroup via Stock.Stock (Override TK '[ Int via Mon.Sum ])++-- position-keyed Override: field 0 via Sum, field 1 via Product+data PK = PK Int Int+ deriving (Eq, Show)+ deriving Semigroup+ via Stock.Stock (Override PK [ PK at 0 via Mon.Sum, PK at 1 via Mon.Product ])++-- positional [[..]] Override: one inner list per constructor, one element per+-- field. @_@ (lowered to 'Keep' by the surface pass) leaves a field alone, so+-- this overrides only the first two fields and keeps the @[Int]@ as-is.+-- Outer list is ticked (single-element ⇒ would otherwise parse as the list type).+data Pos = Pos Int Int [Int]+ deriving (Eq, Show)+ deriving Semigroup+ via Stock.Stock (Override Pos '[ [Mon.Sum, Mon.Product, _] ])++-- the canonical example: @[[Sum Int, _, _]]@ changes only the first field of the+-- first constructor (a /saturated/ @Sum Int :: Type@ modifier, pinned to the+-- field's @Int@); the rest are kept. 'Keep' is poly-kinded, so it sits in a+-- @[Type]@ list here just as it sat in the @[Type -> Type]@ list above.+data PosS = PosS Int [Int] [Int]+ deriving (Eq, Show)+ deriving Semigroup+ via Stock.Stock (Override PosS '[ [Mon.Sum Int, _, _] ])++-- multi-constructor --> paths, observed through the (SDK-native) Eq: a field+-- overridden to 'Mod5' compares modulo 5. @'MA --> 0 --> Mod5@ targets only+-- MA's first field; @'MB --> Mod5@ every field of MB. Mod5 is a saturated+-- (pinned) modifier — Coercible Int Mod5.+newtype Mod5 = Mod5 Int+instance Eq Mod5 where Mod5 a == Mod5 b = a `mod` 5 == b `mod` 5+data Multi = MA Int Int | MB Int+ deriving Show+ deriving Eq+ via Stock.Stock (Override Multi '[ 'MA --> 0 --> Mod5, 'MB --> Mod5 ])++-- Ord now respects Override too (was a viaSynth holdout): field0 via Down+-- reverses its comparison, field1 stays normal.+data OrdOv = OrdOv Int Int+ deriving (Eq, Show)+ deriving Ord via Stock.Stock (Override OrdOv '[ [Down, _] ])++-- Show + Read both respect Override: showing field0 as a 'Sum' and reading it+-- back (coercing to Int) round-trips — proving both directions honour it.+data SR = SR Int Int+ deriving stock Eq+ deriving (Show, Read) via Stock.Stock (Override SR '[ [Mon.Sum, _] ])++-- The payoff: Generic respects Override, so @Generically (Override A cfg)@+-- derives /any/ Generically class over the overridden fields. Here Semigroup+-- combines field0 additively (Sum) and field1 multiplicatively (Product) —+-- driven entirely through the Generic Rep, no Stock-Semigroup deriver.+data CoordG = CoordG Int Int+ deriving (Eq, Show)+ deriving Semigroup+ via Generically (Overriding CoordG '[ [Mon.Sum, Mon.Product] ])++-- ===== Override across the remaining classes that honour it =====++-- Monoid: mempty/mappend through Sum (additive) + Product (multiplicative); the+-- identities are 0 and 1, not Int's (which has no Monoid).+data MonOv = MonOv Int Int deriving (Eq, Show)+ deriving (Semigroup, Monoid)+ via Stock.Stock (Override MonOv '[ [Mon.Sum, Mon.Product] ])++-- Bounded over a product: field0's bounds come from Hi (100..200), not Int's.+newtype Hi = Hi Int deriving (Eq, Show)+instance Bounded Hi where { minBound = Hi 100 ; maxBound = Hi 200 }+data BdOv = BdOv Int Bool deriving (Eq, Show)+ deriving Bounded via Stock.Stock (Override BdOv '[ [Hi, _] ])++-- Enum / Ix are enum-only (no fields): an all-blank config is the identity,+-- so Override neither breaks nor changes them.+data EnOv = EnA | EnB | EnC deriving (Eq, Show)+ deriving (Enum, Ix, Ord) via Stock.Stock (Override EnOv '[ '[], '[], '[] ])++-- top-level empty config @'[]@ on a type WITH fields is the identity: exactly+-- like plain @Stock@ (regression: @'[]@ was mis-read as a 0-constructor+-- positional config and rejected).+data EmptyOv = EmptyOv Int Bool+ deriving (Eq, Show) via Stock.Stock (Override EmptyOv '[])++-- Functor via Override1 with an observable, law-breaking modifier: @Blah@ counts+-- each @fmap@ in its @Int@ slot. The field @(Int, a)@ is reshaped to @Blah@, so+-- mapping bumps the counter — visibly proving the override is honoured.+newtype Blah a = Blah (Int, a)+instance Functor Blah where fmap f (Blah (n, a)) = Blah (1 + n, f a)+data WithCount a = WithCount (Int, a) deriving (Eq, Show)+ deriving Functor via Overriding1 WithCount '[ '[Blah] ]++-- Contravariant via Override1: the Predicate field reshaped to Neg, whose+-- contramap negates the result (the one observable tweak that stays well-typed).+newtype Neg a = Neg (Predicate a)+instance Contravariant Neg where+ contramap f (Neg (Predicate p)) = Neg (Predicate (not . p . f))+newtype CV a = CV (Predicate a)+ deriving Contravariant via Overriding1 CV '[ '[Neg] ]+runCV :: CV a -> a -> Bool+runCV (CV (Predicate p)) = p++-- Bifunctor via Override2: each list field reshaped to RevL, whose fmap reverses,+-- so bimap reverses both lists.+newtype RevL a = RevL [a]+instance Functor RevL where fmap f (RevL xs) = RevL (reverse (map f xs))+data B2 a b = B2 [a] [b] deriving (Eq, Show)+ deriving Functor via Stock.Stock1 (B2 a)+ deriving Bifunctor via Overriding2 B2 '[ '[RevL, RevL] ]++-- Override1 / Override2 with the SAME field-keyed surface as value Override,+-- only at a different modifier kind (a functor here) — and in the bare-lowercase+-- plugin notation (@nkXs := m@ / @fld via m@), lowered by the source plugin.+data NK a = NK { nkXs :: [a] } deriving (Eq, Show)+ deriving Functor via Overriding1 NK '[ nkXs := RevL ]+data NK2 a b = NK2 { nk2a :: [a], nk2b :: [b] } deriving (Eq, Show)+ deriving Functor via Stock.Stock1 (NK2 a)+ deriving Bifunctor via Overriding2 NK2 '[ nk2a via RevL, nk2b via RevL ]++-- A blind/reversing list modifier (coercible to [a]) for the lifted comparison+-- + folding classes: Eq1/Ord1 blind (all equal), Show1 fixed, Foldable reversed.+newtype BL a = BL [a]+instance Eq1 BL where liftEq _ _ _ = True+instance Ord1 BL where liftCompare _ _ _ = EQ+instance Show1 BL where liftShowsPrec _ _ _ _ = showString "BL"+instance Foldable BL where foldMap f (BL xs) = foldMap f (reverse xs)+-- base instances (the quantified superclasses of BL's lifted instances), blind to match+instance Eq (BL a) where _ == _ = True+instance Ord (BL a) where compare _ _ = EQ+instance Show (BL a) where showsPrec _ _ = showString "BL"++-- Eq1 / Ord1 / Show1 via Override1 (the [a] field through BL). The base+-- Eq/Ord/Show satisfy the lifted classes' quantified superclasses.+data Lc a = Lc [a] deriving (Eq, Ord, Show)+ deriving Eq1 via Overriding1 Lc '[ '[BL] ]+ deriving Ord1 via Overriding1 Lc '[ '[BL] ]+ deriving Show1 via Overriding1 Lc '[ '[BL] ]++-- Eq2 / Ord2 / Show2 / Bifoldable via Override2 (both fields through BL). The+-- one-parameter lifted instances (superclasses of the two-parameter ones) are+-- plain Stock1.+data Bc a b = Bc [a] [b] deriving (Eq, Ord, Show)+ deriving (Eq1, Ord1, Show1) via Stock.Stock1 (Bc a)+ deriving Eq2 via Overriding2 Bc '[ '[BL, BL] ]+ deriving Ord2 via Overriding2 Bc '[ '[BL, BL] ]+ deriving Show2 via Overriding2 Bc '[ '[BL, BL] ]+ deriving Bifoldable via Overriding2 Bc '[ '[BL, BL] ]++-- Generic1 via Override1 → Applicative via Generically1: the [a] field reshaped+-- to ZipList, so the /generically/-derived Applicative ZIPS instead of the+-- cartesian []-product. (Proves Generic1 honours Override1 at the Rep1 level.)+data Zg a = Zg [a]+ deriving (Eq, Show)+ deriving Generic1 via Overriding1 Zg '[ '[ZipList] ]+ deriving (Functor, Applicative) via Generically1 Zg+runZg :: Zg a -> [a]+runZg (Zg xs) = xs++-- `_` (Keep) sugar in an Override1 positional config: an identity reshape (the+-- field is left as []). Confirms the source plugin lowers `_` for the+-- Overriding1 wrapper too, not just value Override.+data Kp a = Kp [a] deriving (Eq, Show)+ deriving Functor via Overriding1 Kp '[ '[_] ]++-- A reversing list modifier (coercible to [a]) whose Read1 reads a list then+-- /reverses/ it — observably different from []'s, so a parsed value reflects the+-- override. Read1's quantified superclass needs a matching base Read (RL a).+newtype RL a = RL [a]+instance Read1 RL where+ liftReadsPrec rp rl d s = [ (RL (reverse ys), s') | (ys, s') <- liftReadsPrec rp rl d s ]+instance Read a => Read (RL a) where+ readsPrec d s = [ (RL (reverse ys), s') | (ys, s') <- readsPrec d s ]++-- Read1 via Override1: reading @"Lr [1,2,3]"@ parses the field through RL, so the+-- list comes back reversed — proof the modifier is honoured (plain [] would give+-- @Lr [1,2,3]@).+data Lr a = Lr [a] deriving (Eq, Show, Read)+ deriving Read1 via Overriding1 Lr '[ '[RL] ]++-- Read2 via Override2: both list fields parsed through RL ⇒ both come back+-- reversed. Read2's superclass needs a plain Read1 (Br a).+data Br a b = Br [a] [b] deriving (Eq, Show, Read)+ deriving Read1 via Stock.Stock1 (Br a)+ deriving Read2 via Overriding2 Br '[ '[RL, RL] ]++++-- For the representational-fidelity check: 'Gen' has GHC's *stock* Generic+-- (giving the real @Rep Gen@), while the plugin provides @Generic (Stock Gen)@.+-- The two Reps differ only by newtype @M1@/@K1@ layers, so they are 'Coercible'.+data Gen = Gen [Int] [Int]+ deriving (Eq, Generic)++-- A SUM type with stock Generic, for the sum version of the cross-Rep round-trip.+data GenS = GA | GB Int | GC Int Bool+ deriving (Eq, Generic)++-- For metadata (M1) checks: a single-constructor record.+data MetaR = MetaR { mfield :: Int }+ deriving Generic via Stock.Stock MetaR++-- Cross-validation: stock @Generic Gen@ and the plugin's @Generic (Stock Gen)@+-- must drive the SAME @Generically@ algorithm to the SAME result. We compute+-- @(<>)@ / @mempty@ both ways on the same value (bridging with 'coerce') and+-- compare — a behavioural proof that the synthesized Rep equals stock's.+viaGen, viaStockGen :: Gen -> Gen -> Gen+viaGen a b = coerce ((coerce a :: Generically Gen) <> coerce b)+viaStockGen a b = coerce ((coerce a :: Generically (Stock.Stock Gen)) <> coerce b)+memptyGen, memptyStockGen :: Gen+memptyGen = coerce (mempty :: Generically Gen)+memptyStockGen = coerce (mempty :: Generically (Stock.Stock Gen))++-- Functor via Stock1 (parameter field, constant field, functor field)+data Trio a = Trio Int a [a]+ deriving (Eq, Ord, Show, Read) via Stock.Stock (Trio a)+ deriving Functor via Stock.Stock1 Trio+ deriving Foldable via Stock.Stock1 Trio+ -- Eq1/Ord1: Int field (own Eq/Ord), the parameter (supplied fn), [a] (lifted)+ deriving (Eq1, Ord1) via Stock.Stock1 Trio+ deriving Show1 via Stock.Stock1 Trio+ deriving Read1 via Stock.Stock1 Trio+data Trio' a = Trio' Int a [a] deriving (Eq, Show, Functor, Foldable)++-- Applicative via Stock1 handles a constant field Const-style (needs Monoid),+-- exactly as Generically1: pure fills it with mempty, <*> combines it with (<>).+data Ap a = Ap [Int] a+ deriving (Eq, Show)+ deriving (Functor, Applicative) via Stock.Stock1 Ap++-- Override1: the [a] field reshaped to ZipList, so Applicative ZIPS (instead of+-- the cartesian product []), and Functor is unchanged.+data Zl a = Zl [a]+ deriving (Eq, Show)+ deriving Functor via Overriding1 Zl '[ '[ZipList] ]+ deriving Applicative via Overriding1 Zl '[ '[ZipList] ]+ deriving Foldable via Overriding1 Zl '[ '[ZipList] ]++-- Traversable: the instance is SYNTHESIZED at @Stock1 _@ (DerivingVia can't+-- coerce it onto the type — abstract-applicative nominal role), and put on the+-- type with the one-liner. Trav is recursive (param + recursive-functor + list+-- fields); Trav' is GHC's own stock-derived oracle.+data Trav a = TLeaf | TNode (Trav a) a [a]+ deriving (Eq, Show)+ deriving (Functor, Foldable) via Stock.Stock1 Trav+instance Traversable Trav where+ traverse g = fmap Stock.unStock1 . traverse g . Stock.Stock1+data Trav' a = TLeaf' | TNode' (Trav' a) a [a]+ deriving (Eq, Show, Functor, Foldable, Traversable)++-- Nested + tuple fields: Functor/Foldable/Traversable must match GHC's full+-- structural walk (nested [[a]], Maybe [a], tuple (a,a)). NestG is GHC's oracle.+data Nest a = Nest [[a]] (Maybe [a]) (a, a) a+ deriving (Eq, Show)+ deriving (Functor, Foldable) via Stock.Stock1 Nest+instance Traversable Nest where+ traverse g = fmap Stock.unStock1 . traverse g . Stock.Stock1+data NestG a = NestG [[a]] (Maybe [a]) (a, a) a+ deriving (Eq, Show, Functor, Foldable, Traversable)+nestVal :: ([[Int]], Maybe [Int], (Int, Int), Int)+nestVal = ([[1,2],[3]], Just [4,5], (6,7), 8)+cNe :: Nest Int -> ([[Int]], Maybe [Int], (Int, Int), Int)+cNe (Nest a b c d) = (a, b, c, d)+cNeG :: NestG Int -> ([[Int]], Maybe [Int], (Int, Int), Int)+cNeG (NestG a b c d) = (a, b, c, d)++-- Ix on a single-constructor PRODUCT (GHC derives it; we now match): range is+-- the Cartesian product, index mixed-radix. IxPG is GHC's stock twin.+data IxP = IxP Int Bool deriving (Eq, Ord, Show)+ deriving Ix via Stock.Stock IxP+data IxPG = IxPG Int Bool deriving (Eq, Ord, Show, Ix)+cIxP :: IxP -> (Int, Bool)+cIxP (IxP a b) = (a, b)+cIxPG :: IxPG -> (Int, Bool)+cIxPG (IxPG a b) = (a, b)++-- Generic META parity: infix constructor fixity (#1) and field strictness (#3)+-- must match GHC's derived Rep (phantom type-level meta, vs twins).+infixr 7 :*:.+data MOp = Int :*:. Int deriving (Eq, Show) ; deriving via Stock.Stock MOp instance Generic MOp+infixr 7 :*:~+data MOpG = Int :*:~ Int deriving (Eq, Show, Generic)+data MStr = MStr ![Int] Int deriving (Eq, Show) ; deriving via Stock.Stock MStr instance Generic MStr+data MStrG = MStrG ![Int] Int deriving (Eq, Show, Generic)+data MSum = MN | MP Int Bool | MR { mrf :: Int, mrg :: Bool } deriving (Eq, Show)+deriving via Stock.Stock MSum instance Generic MSum++-- STATIC Rep parity: normalize only the module/package strings in the outer D1+-- (those legitimately differ Main vs Twin), then assert the ENTIRE Rep type —+-- datatype name + isNewtype, constructor fixity (#1), selector strictness (#3),+-- record/field meta, and the balanced sum/product shape — is IDENTICAL to GHC's+-- derived Rep. Any divergence is a compile-time type error.+type family DummyMP (r :: Type -> Type) :: Type -> Type where+ DummyMP (D1 ('MetaData n _ _ nt) x) = D1 ('MetaData n "M" "P" nt) x+repParityMOp :: DummyMP (Rep MOp) () :~: DummyMP (Rep Twin.MOp) ()+repParityMOp = Refl+repParityMStr :: DummyMP (Rep MStr) () :~: DummyMP (Rep Twin.MStr) ()+repParityMStr = Refl+repParityMSum :: DummyMP (Rep MSum) () :~: DummyMP (Rep Twin.MSum) ()+repParityMSum = Refl++-- Ord relational ops (#6): a small (<=3-con) product gets direct <,<=,>,>= ;+-- they must agree with GHC's derived twin for every pair.+data OrdT = OA | OB Int Bool+ deriving (Eq, Show) deriving Ord via Stock.Stock OrdT+data OrdTG = OAg | OBg Int Bool deriving (Eq, Show, Ord)+cOrdT :: OrdT -> OrdTG+cOrdT OA = OAg ; cOrdT (OB i b) = OBg i b+ordVals :: [OrdT]+ordVals = [OA, OB 1 True, OB 1 False, OB 2 False, OB 2 True]++data KTr = KLeaf | KNode KTr Int [Int] deriving (Eq, Show)+cTr :: Trav Int -> KTr+cTr TLeaf = KLeaf ; cTr (TNode l x xs) = KNode (cTr l) x xs+cTr' :: Trav' Int -> KTr+cTr' TLeaf' = KLeaf ; cTr' (TNode' l x xs) = KNode (cTr' l) x xs++-- Override1 + Traversable: the [a] field traverses through ZipList's Traversable.+instance Traversable Zl where+ traverse g = fmap Stock.unStock1 . traverse g . Stock.Stock1++-- Bitraversable: synthesized at @Stock2 _@ + the one-liner. GHC has no+-- stock @deriving Bitraversable@, so we check the law @bitraverse (Just . f)+-- (Just . g) = Just . bimap f g@ (and identity / short-circuit).+data BT a b = BTNil | BTBoth a b | BTList a [b] Int+ deriving (Eq, Show)+ deriving (Functor, Foldable) via Stock.Stock1 (BT a)+ deriving (Bifunctor, Bifoldable) via Stock.Stock2 BT+instance Bitraversable BT where+ bitraverse f g = fmap Stock.unStock2 . bitraverse f g . Stock.Stock2++runZl :: Zl a -> [a]+runZl (Zl xs) = xs++-- @f@ is abstract, so @Eq (f a)@ / @Ord (f a)@ / @Show (f a)@ can only come+-- from the quantified superclass of @Eq1 f@ / @Ord1 f@ / @Show1 f@.+eqViaEq1 :: (Eq1 f, Eq a) => f a -> f a -> Bool+eqViaEq1 = (==)+cmpViaOrd1 :: (Ord1 f, Ord a) => f a -> f a -> Ordering+cmpViaOrd1 = compare+showViaShow1 :: (Show1 f, Show a) => f a -> String+showViaShow1 x = show x++-- a parameterised record, to exercise Show1's record path (K {l = v, …})+data Recd a = Recd { rx :: a, ry :: [a] }+ deriving (Eq, Show, Read) via Stock.Stock (Recd a)+ deriving Show1 via Stock.Stock1 Recd+ deriving Read1 via Stock.Stock1 Recd++-- @f@ abstract ⇒ Read (f a) can only come from the quantified Read1 superclass+readViaRead1 :: (Read1 f, Read a) => String -> f a+readViaRead1 = read++-- Generic1 via Stock1: Par1 (@a@), Rec1 (@[a]@), Rec0 (@Int@), and @:.:@+-- composition (@[[a]]@ = @[] :.: Rec1 []@).+data G1 a = G1 Int a [a] [[a]] | G1' a+ deriving (Eq, Show)+ deriving Generic1 via Stock.Stock1 G1++-- Contravariant via Stock1: the parameter only in negative positions. GHC has+-- no stock 'deriving Contravariant', so we check against the laws directly.+-- 'Sel' mixes a function field (negative), a constant, and a 'Pred' subfield+-- (itself contravariant) — and 'Pred' is a newtype (unwrapped by coercion).+newtype Pred a = Pred (a -> Bool)+ deriving Contravariant via Stock.Stock1 Pred+data Sel r a = Sel (a -> r) Int (Pred a)+ deriving Contravariant via Stock.Stock1 (Sel r)++runPred :: Pred a -> a -> Bool+runPred (Pred p) = p++-- Variance fidelity: the parameter under nested function arrows. @(a -> Int)+-- -> Int@ is double-negative ⇒ covariant ⇒ a 'Functor' (GHC's stock+-- DeriveFunctor accepts it too); the triple-nested one is contravariant.+newtype Cps a = Cps ((a -> Int) -> Int)+ deriving Functor via Stock.Stock1 Cps+runCps :: Cps a -> (a -> Int) -> Int+runCps (Cps g) = g+newtype Cps3 a = Cps3 (((a -> Int) -> Int) -> Int)+ deriving Contravariant via Stock.Stock1 Cps3+forceCps3 :: Cps3 a -> ()+forceCps3 (Cps3 g) = g `seq` ()++-- multi-argument contravariant field (parameter in two negative positions)+newtype Foo2 a = Foo2 (a -> a -> Int)+ deriving Contravariant via Stock.Stock1 Foo2+runFoo2 :: Foo2 a -> a -> a -> Int+runFoo2 (Foo2 h) = h++-- Bifunctor / Bifoldable via Stock2 (mix of a-, b-, [b]- and constant fields).+-- Bifunctor's quantified superclass forall a. Functor (Bi a) needs Functor too.+data Bi a b = Bi a b | OnlyA a | Bs b [b] | Tag Int+ deriving (Eq, Ord, Show, Read)+ deriving (Functor, Eq1, Ord1, Show1, Read1) via Stock.Stock1 (Bi a)+ deriving (Bifunctor, Bifoldable, Eq2, Ord2, Show2, Read2) via Stock.Stock2 Bi++-- Bifunctor with a NESTED bifunctor field (@Either a b@) and a nested covariant+-- field (@[b]@) — both reached by the n-ary variance engine (the self-app case+-- for @Either a b@; deep functor recursion for @[b]@), which the flat+-- 'classifyBiField' could not map.+data BiE a b = BiE (Either a b) [b]+ deriving (Eq, Show)+ deriving Functor via Stock.Stock1 (BiE a)+ deriving Bifunctor via Stock.Stock2 BiE++-- Category via Stock2: pointwise id/(.) over a single-constructor product whose+-- fields are each a Category in the two params (here (:~:) and (->)).+data P2 a b = P2 (a :~: b) (a -> b)+ deriving Category via Stock.Stock2 P2++runP2 :: P2 a b -> (a -> b)+runP2 (P2 _ f) = f++-- Category with a CONSTANT field (Sum Int): handled Const-style via Monoid+-- (id = mempty, (.) = (<>)) — no Basic / Override2 needed.+data LC a b = LC (Mon.Sum Int) (a -> b)+ deriving Category via Stock.Stock2 LC++runLC :: LC a b -> (Mon.Sum Int, a -> b)+runLC (LC s f) = (s, f)++-- A trivial Category that ignores its parameters and just accumulates a monoid.+newtype Basic m a b = Basic m+instance Monoid m => Category (Basic m) where+ id :: Basic m a a+ id = Basic mempty+ (.) :: Basic m b c -> Basic m a b -> Basic m a c+ Basic x . Basic y = Basic (x <> y)++-- The payoff: fields that are NOT yet categories (an Int, a String, an+-- @a -> Maybe b@) are reshaped by Override2 into ones — Basic (Sum Int),+-- Basic String, Kleisli Maybe — and Category is then derived pointwise.+data Foo a b = Foo Int String (a -> Maybe b)+ deriving Category+ via Overriding2 Foo '[ '[ Basic (Mon.Sum Int), Basic String, Kleisli Maybe ] ]++runFoo :: Foo a b -> (Int, String, a -> Maybe b)+runFoo (Foo i s f) = (i, s, f)++-- Run a value through stock @from@, 'coerce' between the two (representationally+-- equal) Reps, then bring it back with the plugin's @to@. Compiles only if+-- @Rep (Stock Gen) ~R Rep Gen@, and round-trips only if @to@ is correct.+repCrossRoundtrip :: Gen -> Gen+repCrossRoundtrip g =+ Stock.unStock (to (coerce (from g :: Rep Gen ()) :: Rep (Stock.Stock Gen) ()))++-- same, for a SUM type: exercises the @:+:@ structure across the two Reps+repCrossRoundtripS :: GenS -> GenS+repCrossRoundtripS g =+ Stock.unStock (to (coerce (from g :: Rep GenS ()) :: Rep (Stock.Stock GenS) ()))++-- parameterised INFIX types: exercise Read1/Read2 ambiguous-parse ORDER, which+-- only the ReadPrec-based synthesis matches (plain prefix/record can't show it).+infixr 5 :+++data InfF a = ILit a | InfF a :++ InfF a+ deriving (Eq, Show)+ deriving Read via Stock.Stock (InfF a) -- Read1's quantified superclass needs it+ deriving Read1 via Stock.Stock1 InfF+infixr 5 :**+data InfB a b = IB a b | a :** b+ deriving (Eq, Show)+ deriving Read via Stock.Stock (InfB a b)+ deriving Read1 via Stock.Stock1 (InfB a)+ deriving Read2 via Stock.Stock2 InfB++-- ----- stock-derived twins (the oracle) -----------------------------------++data Color' = Red' | Green' | Blue'+ deriving (Eq, Ord, Show, Enum, Bounded)+data Sum' = A' | B' Int | C' Int Bool | Rec' { rf' :: Int, rg' :: Bool }+ deriving (Eq, Ord, Show)++-- drop the primes and map the twin operators (@:+.@/@:*.@) back to ours+-- (@:+:@/@:*:@) so a twin's `show` matches ours textually+norm :: String -> String+norm = map (\c -> if c == '.' then ':' else c) . filter (/= '\'')++-- ----- Read parity vs GHC stock (the strong check) -------------------------+--+-- Round-trips only prove @read . show = id@. These compare the FULL @readsPrec@+-- ReadS result (parsed value + leftover string + list order/length) of the+-- plugin's instance against GHC's own derived @Read@ on a name-identical twin,+-- over valid / whitespaced / parenthesised / negative / garbage / trailing-junk+-- inputs at several precedences. Equality of the lists == identical behaviour.++-- neutral canonical forms (so the two distinct twin types are comparable)+data KS = KA | KB Int | KC Int Bool | KRec Int Bool deriving (Eq, Show)+data KE = KLit Int | KAdd KE KE | KMul KE KE deriving (Eq, Ord, Show)+data KBi = KBi Int Bool | KOnlyA Int | KBs Bool [Bool] | KTag Int deriving (Eq, Show)++cS :: Sum -> KS+cS A = KA; cS (B i) = KB i; cS (C i b) = KC i b; cS (Rec i b) = KRec i b+cST :: Twin.Sum -> KS+cST Twin.A = KA; cST (Twin.B i) = KB i; cST (Twin.C i b) = KC i b; cST (Twin.Rec i b) = KRec i b++cE :: Expr -> KE+cE (Lit i) = KLit i; cE (a :+: b) = KAdd (cE a) (cE b); cE (a :*: b) = KMul (cE a) (cE b)+cET :: Twin.Expr -> KE+cET (Twin.Lit i) = KLit i+cET (a Twin.:+: b) = KAdd (cET a) (cET b)+cET (a Twin.:*: b) = KMul (cET a) (cET b)++cT :: Trio Int -> (Int, Int, [Int])+cT (Trio i a xs) = (i, a, xs)+cTT :: Twin.Trio Int -> (Int, Int, [Int])+cTT (Twin.Trio i a xs) = (i, a, xs)++cR :: Recd Int -> (Int, [Int])+cR (Recd x ys) = (x, ys)+cRT :: Twin.Recd Int -> (Int, [Int])+cRT (Twin.Recd x ys) = (x, ys)++cB :: Bi Int Bool -> KBi+cB (Bi a b) = KBi a b; cB (OnlyA a) = KOnlyA a; cB (Bs b xs) = KBs b xs; cB (Tag i) = KTag i+cBT :: Twin.Bi Int Bool -> KBi+cBT (Twin.Bi a b) = KBi a b; cBT (Twin.OnlyA a) = KOnlyA a+cBT (Twin.Bs b xs) = KBs b xs; cBT (Twin.Tag i) = KTag i++data KInf = KIL Int | KIAdd KInf KInf deriving (Eq, Ord, Show)+cInf :: InfF Int -> KInf+cInf (ILit n) = KIL n; cInf (a :++ b) = KIAdd (cInf a) (cInf b)+cInfT :: Twin.InfF Int -> KInf+cInfT (Twin.ILit n) = KIL n; cInfT (a Twin.:++ b) = KIAdd (cInfT a) (cInfT b)++data KIB = KIB Int Bool | KIBop Int Bool deriving (Eq, Ord, Show)+cIB :: InfB Int Bool -> KIB+cIB (IB a b) = KIB a b; cIB (a :** b) = KIBop a b+cIBT :: Twin.InfB Int Bool -> KIB+cIBT (Twin.IB a b) = KIB a b; cIBT (a Twin.:** b) = KIBop a b++-- mismatches between plugin ReadS and twin ReadS over (precision, input) pairs+parityAt :: Eq k+ => [Int] -> (Int -> ReadS a) -> (Int -> ReadS b)+ -> (a -> k) -> (b -> k) -> [String] -> [(Int, String, [(k, String)], [(k, String)])]+parityAt precs rp rpT ca cb inputs =+ [ (p, s, l, r)+ | p <- precs, s <- inputs+ , let l = map (Data.Bifunctor.first ca) (rp p s)+ r = map (Data.Bifunctor.first cb) (rpT p s)+ , l /= r ]++parityCheck :: (Eq k, Show k)+ => String -> [Int] -> (Int -> ReadS a) -> (Int -> ReadS b)+ -> (a -> k) -> (b -> k) -> [String] -> IO Bool+parityCheck = parityCheckWith id++-- Read1/Read2 entry via the ReadPrec methods with NATIVE readPrec leaves (the+-- fair oracle: the default ReadS entry @liftReadsPrec readsPrec readList@ wraps+-- leaves with @readS_to_Prec@, which perturbs ReadP result order independently+-- of the synthesis).+rp1 :: (Read1 f, Read a) => Int -> ReadS (f a)+rp1 = readPrec_to_S (liftReadPrec readPrec readListPrec)+rp2 :: (Read2 f, Read a, Read b) => Int -> ReadS (f a b)+rp2 = readPrec_to_S (liftReadPrec2 readPrec readListPrec readPrec readListPrec)++-- For ambiguous INFIX grammars the SET of parses is identical but the list+-- ORDER differs (GHC's 'ReadPrec' search vs our 'ReadS' append) — so we compare+-- as a multiset. This is the one documented ReadS-vs-ReadPrec residual; @read@+-- and any unique-parse input are unaffected (a single result has no order).+parityCheckU :: (Ord k, Show k)+ => String -> [Int] -> (Int -> ReadS a) -> (Int -> ReadS b)+ -> (a -> k) -> (b -> k) -> [String] -> IO Bool+parityCheckU = parityCheckWith Data.List.sort++parityCheckWith :: (Eq k, Show k)+ => ([(k, String)] -> [(k, String)])+ -> String -> [Int] -> (Int -> ReadS a) -> (Int -> ReadS b)+ -> (a -> k) -> (b -> k) -> [String] -> IO Bool+parityCheckWith norm0 name precs rp rpT ca cb inputs = do+ let ms = [ m | m@(_, _, l, r) <- parityAt precs rp rpT ca cb inputs, norm0 l /= norm0 r ]+ mapM_ (\(p, s, l, r) -> putStrLn (unlines+ [ " prec=" ++ show p ++ " input=" ++ show s+ , " via Stock: " ++ show l+ , " GHC stock: " ++ show r ])) ms+ check name (null ms)++sumInputs, exprInputs, trioInputs, recdInputs, biInputs :: [String]+sumInputs =+ [ "A", "B 5", "B (-5)", "C 1 True", "C 0 False"+ , "Rec {rf = 3, rg = False}", "Rec {rf=3,rg=True}", "Rec { rf = 1 , rg = True }"+ , " A ", " B 7 ", "(A)", "((B 9))", "(C 1 True)"+ , "", "B", "B x", "Zzz", "C 1", "A xyz", "B 5 rest"+ , "Rec {rf=1}", "Rec {rf=1, foo=2}", "B 5.0", "-3", "B 0x10" ]+exprInputs =+ [ "Lit 1", "Lit (-2)", "Lit 1 :+: Lit 2", "Lit 1 :+: Lit 2 :+: Lit 3"+ , "Lit 1 :*: Lit 2 :+: Lit 3", "Lit 1 :+: Lit 2 :*: Lit 3"+ , "(Lit 1 :+: Lit 2) :*: Lit 3", "Lit 1 :*: (Lit 2 :+: Lit 3)"+ , " Lit 1 :+: Lit 2 ", "((Lit 1))"+ , "", "Lit", "Lit 1 :+:", ":+: Lit 1", "Lit 1 :+: Lit 2 rest" ]+trioInputs =+ [ "Trio 1 2 [3,4]", "Trio (-1) (-2) [-3,-4]", " Trio 0 0 [] "+ , "(Trio 1 2 [3])", "", "Trio 1 2", "Trio 1 2 [3,4] rest", "Trio 1 2 3" ]+recdInputs =+ [ "Recd {rx = 1, ry = [2,3]}", "Recd {rx=0, ry=[]}", " Recd { rx = 5 , ry = [1] } "+ , "", "Recd {rx=1}", "Recd {rx=1, ry=[2], z=3}" ]+biInputs =+ [ "Bi 1 True", "OnlyA 5", "OnlyA (-5)", "Bs True [False,True]", "Tag 9", "Tag (-9)"+ , " Bi 1 True ", "(OnlyA 5)", "", "Bi 1", "Bs True", "Zzz", "Bi 1 True rest" ]+infInputs, ibInputs :: [String]+infInputs =+ [ "ILit 1", "ILit (-2)", "ILit 1 :++ ILit 2", "ILit 1 :++ ILit 2 :++ ILit 3"+ , "ILit 1 :++ (ILit 2 :++ ILit 3)", "(ILit 1 :++ ILit 2) :++ ILit 3"+ , " ILit 1 :++ ILit 2 ", "", "ILit", "ILit 1 :++", "ILit 1 :++ ILit 2 rest" ]+ibInputs =+ [ "IB 1 True", "1 :** True", "(IB 1 True)", "(1 :** True)", " 1 :** True "+ , "", "IB 1", "1 :**", "IB 1 True rest" ]++-- ----- tiny assertion harness ---------------------------------------------++check :: String -> Bool -> IO Bool+check name ok = do+ putStrLn ((if ok then "ok " else "FAIL ") ++ name)+ pure ok++-- True if forcing @x@ throws (GHC's derived toEnum/succ/pred error out of range;+-- ours must too, not segfault).+throws :: a -> IO Bool+throws x = do+ r <- try (evaluate (x `seq` ())) :: IO (Either SomeException ())+ pure (either (const True) (const False) r)++main :: IO ()+main = do+ enumOOB <- throws (toEnum 99 :: Color)+ enumSucc <- throws (succ Blue)+ enumPred <- throws (pred Red)+ rs <- sequence+ [ -- Eq / Ord against twins+ check "Eq enum" (Red == Red && Red /= Blue)+ , check "Ord enum" (compare Blue Red == compare Blue' Red')+ , check "Ord fields" (compare (C 1 True) (C 1 False) == GT && B 1 < C 0 False)+ , check "Ord lexico" ([minimum xs, maximum xs] == [A, Rec 9 True])+ -- #6: direct <,<=,>,>= for a small type agree with GHC's derived twin+ , check "Ord rel ops" (and [ (x < y) == (cOrdT x < cOrdT y)+ && (x <= y) == (cOrdT x <= cOrdT y)+ && (x > y) == (cOrdT x > cOrdT y)+ && (x >= y) == (cOrdT x >= cOrdT y)+ | x <- ordVals, y <- ordVals ])+ -- Show against twins (record, prefix, nesting, negatives)+ , check "Show enum" (show Green == norm (show Green'))+ , check "Show prefix" (show (C 1 True) == norm (show (C' 1 True)))+ , check "Show neg" (show (B (-5)) == norm (show (B' (-5))))+ , check "Show record" (show (Rec 3 True) == norm (show (Rec' 3 True)))+ , check "Show nested" (show (Just (B 7)) == norm (show (Just (B' 7))))+ -- Read round-trips+ , check "Read enum" (read "Green" == Green)+ , check "Read rt prefix"(read (show (C 4 False)) == C 4 False)+ , check "Read rt record"(read (show (Rec 5 False)) == Rec 5 False)+ , check "Read paren/ws" (read " (B (-2)) " == B (-2))+ -- Read PARITY: full readsPrec ReadS output == GHC's own derived Read+ , parityCheck "Read parity Sum" [0,11] readsPrec readsPrec cS cST sumInputs+ , parityCheck "Read parity Expr" [0,6,7,11] readsPrec readsPrec cE cET exprInputs+ , parityCheck "Read parity Trio" [0,11] readsPrec readsPrec cT cTT trioInputs+ , parityCheck "Read parity Recd" [0,11] readsPrec readsPrec cR cRT recdInputs+ -- Read1 PARITY: liftReadPrec (native readPrec leaves) at a concrete type+ -- == GHC's derived Read of the monomorphic twin+ , parityCheck "Read1 parity Trio" [0,11] rp1 readsPrec cT cTT trioInputs+ , parityCheck "Read1 parity Recd" [0,11] rp1 readsPrec cR cRT recdInputs+ -- Read2 PARITY: liftReadPrec2 at concrete types == derived Read of twin+ , parityCheck "Read2 parity Bi" [0,11] rp2 readsPrec cB cBT biInputs+ -- INFIX parity: the ambiguous-parse ORDER (only ReadPrec synthesis matches)+ , parityCheck "Read parity InfF" [0,5,6,11] readsPrec readsPrec cInf cInfT infInputs+ , parityCheck "Read1 parity InfF" [0,5,6,11] rp1 readsPrec cInf cInfT infInputs+ , parityCheck "Read2 parity InfB" [0,5,6,11] rp2 readsPrec cIB cIBT ibInputs+ -- Traversable: synthesized at Stock1, used via the one-liner; behaviour+ -- matches GHC's stock-derived twin (Maybe applicative: success + failure),+ -- and obeys the identity law.+ , check "Traversable rt" (let t = TNode (TNode TLeaf 1 [2,3]) 4 [5 :: Int]+ t' = TNode' (TNode' TLeaf' 1 [2,3]) 4 [5]+ in fmap cTr (traverse (Just . (*10)) t)+ == fmap cTr' (traverse (Just . (*10)) t'))+ , check "Traversable fail"(traverse (\x -> if x == 4 then Nothing else Just x)+ (TNode TLeaf (4 :: Int) [5]) == Nothing)+ , check "Traversable id" (let t = TNode (TNode TLeaf 1 [2,3]) 4 [5 :: Int]+ in runIdentity (traverse Identity t) == t)+ , check "Traversable Ov1" (let z = Zl [1,2,3 :: Int]+ in fmap runZl (traverse Just z) == Just [1,2,3])+ -- Bitraversable: no GHC stock oracle, so check the bimap law + id + failure+ , check "Bitraversable law"(let t = BTList 1 [True,False] 9 :: BT Int Bool+ in bitraverse (Just . (+1)) (Just . not) t+ == Just (bimap (+1) not t))+ , check "Bitraversable id" (let t = BTBoth (7 :: Int) True+ in bitraverse Just Just t == Just t)+ , check "Bitraversable fl" (bitraverse (\x -> if x > 0 then Just x else Nothing) Just+ (BTBoth (-1 :: Int) True) == Nothing)+ -- nested/tuple Functor+Foldable+Traversable must match GHC's full walk+ , check "FFT nest fmap" (let (a,b,c,d) = nestVal+ in cNe (fmap (*10) (Nest a b c d))+ == cNeG (fmap (*10) (NestG a b c d)))+ , check "FFT nest fold" (let (a,b,c,d) = nestVal+ in Data.Foldable.toList (Nest a b c d)+ == Data.Foldable.toList (NestG a b c d))+ , check "FFT nest trav" (let (a,b,c,d) = nestVal+ in fmap cNe (traverse Just (Nest a b c d))+ == fmap cNeG (traverse Just (NestG a b c d)))+ -- parameterised+ , check "param Eq" (Pair (1::Int) 2 == Pair 1 2 && Pair 1 2 /= Pair 1 3)+ , check "param rt" (read (show (Pair (1::Int) 2)) == Pair 1 2)+ -- Enum / Bounded+ , check "Enum from" (map fromEnum [Red ..] == [0,1,2])+ , check "Enum to" (toEnum 1 == Green)+ -- GHC's derived toEnum/succ/pred ERROR out of range; ours must too+ , check "Enum oob" enumOOB+ , check "Enum succ max" enumSucc+ , check "Enum pred min" enumPred+ , check "Enum range" ([Red ..] == [Red, Green, Blue])+ , check "Bounded" ((minBound, maxBound) == (Red, Blue))+ , check "Bounded prod" ((minBound, maxBound) == (BB False LT, BB True GT))+ -- Ix+ , check "Ix range" (range (Red, Blue) == [Red, Green, Blue])+ , check "Ix index" (index (Red, Blue) Green == 1)+ , check "Ix inRange" (inRange (Red, Blue) Green && not (inRange (Green, Blue) Red))+ , check "Ix rangeSize" (rangeSize (Red, Blue) == 3)+ -- Ix on a single-con product == GHC's derived twin (Cartesian range etc.)+ , check "Ix product" (let (lp,up) = (IxP 1 False, IxP 2 True)+ (lg,ug) = (IxPG 1 False, IxPG 2 True)+ in map cIxP (range (lp,up)) == map cIxPG (range (lg,ug))+ && map (index (lp,up)) (range (lp,up))+ == map (index (lg,ug)) (range (lg,ug))+ && rangeSize (lp,up) == rangeSize (lg,ug)+ && and [ inRange (lp,up) (IxP i b) == inRange (lg,ug) (IxPG i b)+ | i <- [0..3], b <- [False,True] ])+ -- Generic META: infix-con fixity (#1) and field strictness (#3) match GHC+ , check "Generic fixity"(conFixity (unM1 (from (1 :*:. 2)))+ == conFixity (unM1 (from (1 :*:~ 2)))+ && conFixity (unM1 (from (1 :*:. 2))) == Infix RightAssociative 7)+ , check "Generic strict"(let sd x = case unM1 (unM1 (from x)) of l G.:*: _ -> selDecidedStrictness l+ in sd (MStr [1] 2) == sd (MStrG [1] 2) && sd (MStr [1] 2) == DecidedStrict)+ -- Generic + Generically (the synthesized Rep bootstraps these)+ , check "Generic rt" (to (from (Prod [1] [2])) == Prod [1] [2])+ , check "Generically <>"(Prod [1] [2] <> Prod [3] [4] == Prod [1,3] [2,4])+ , check "Generically me"(mempty == Prod [] [])+ -- direct pointwise Semigroup/Monoid via Stock (same result as Generically)+ , check "Semigroup <>" (Sg [1] [2] <> Sg [3] [4] == Sg [1,3] [2,4])+ , check "Monoid mempty" (mempty == Sg [] [])+ -- empty config '[] is the identity (same as plain Stock), with fields+ , check "Override '[] id" (show (EmptyOv 1 True) == "EmptyOv 1 True"+ && EmptyOv 1 True == EmptyOv 1 True+ && EmptyOv 1 True /= EmptyOv 2 True)+ -- per-field Override: cx via Sum (additive), cy via Product (multiplicative)+ , check "Override <>" (Coord 2 3 <> Coord 5 7 == Coord 7 21)+ -- positional [[..]]: field0 Sum (additive), field1 Product, field2 _ (kept, ++)+ , check "Override pos" (Pos 2 3 [1] <> Pos 5 7 [2] == Pos 7 21 [1,2])+ -- [[Sum Int, _, _]]: only the first field changes (saturated/pinned)+ , check "Override pos1" (PosS 2 [1] [3] <> PosS 5 [2] [4] == PosS 7 [1,2] [3,4])+ -- multi-ctor --> paths via Eq: 'MA-->0-->Mod5 (field0 mod 5, field1 normal),+ -- 'MB-->Mod5 (MB's field mod 5)+ , check "Override -->" (MA 1 7 == MA 6 7 -- field0 1≡6 (mod5), field1 7=7+ && not (MA 1 7 == MA 1 8) -- field1 normal: 7≠8+ && MB 1 == MB 6 -- MB field 1≡6 (mod5)+ && not (MA 1 7 == MB 1)) -- different constructors+ -- Ord respects Override (viaSynth): field0 via Down reverses, field1 normal+ , check "Override Ord" (compare (OrdOv 1 5) (OrdOv 2 5) == GT+ && compare (OrdOv 5 1) (OrdOv 5 2) == LT)+ -- Show + Read respect Override: round-trip through a Sum-overridden field+ , check "Override S/R" (read (show (SR 3 7)) == SR 3 7)+ -- Generic respects Override: Generically derives Semigroup over the+ -- overridden fields (field0 additive, field1 multiplicative)+ , check "Override Gen" (CoordG 2 5 <> CoordG 3 4 == CoordG 5 20)+ , check "Override type" (TK 2 3 <> TK 5 7 == TK 7 10) -- Int via Sum (both fields)+ , check "Override at" (PK 2 3 <> PK 5 7 == PK 7 21) -- at 0 via Sum, at 1 via Product+ -- Monoid respects Override: mempty = (Sum 0, Product 1), mappend additive/mult.+ , check "Override mempty"(mempty == MonOv 0 1)+ , check "Override <> M" (MonOv 2 3 <> MonOv 5 7 == MonOv 7 21)+ -- Bounded respects Override: field0's bounds come from Hi (100..200)+ , check "Override Bnd" ((minBound, maxBound) == (BdOv 100 False, BdOv 200 True))+ -- Enum / Ix: all-blank Override is the identity on a fieldless enum+ , check "Override Enum" (map fromEnum [EnA ..] == [0,1,2] && toEnum 1 == EnB)+ , check "Override Ix" (range (EnA, EnC) == [EnA, EnB, EnC] && index (EnA, EnC) EnB == 1)+ -- Functor respects Override1: Blah counts the fmap (0 -> 1) while mapping+ , check "Override Functor"(fmap (+ (10 :: Int)) (WithCount (0, 5)) == WithCount (1, 15))+ -- Contravariant respects Override1: Neg negates, so (5+1 > 6 = False) flips to True+ , check "Override Contra"(runCV (contramap (+ (1 :: Int)) (CV (Predicate (> 6)))) 5)+ -- Bifunctor respects Override2: each list field reshaped to RevL ⇒ reversed+ , check "Override Bifun" (bimap (+ (1 :: Int)) not (B2 [1, 2] [True, False])+ == B2 [3, 2] [True, False])+ -- Eq1/Ord1/Show1 respect Override1: BL is blind/fixed+ , check "Override Eq1" (liftEq (==) (Lc [1]) (Lc [9, 9 :: Int])+ && liftCompare compare (Lc [1]) (Lc [9 :: Int]) == EQ)+ , check "Override Show1" (let s = liftShowsPrec showsPrec showList 0 (Lc [1, 2 :: Int]) ""+ in "BL" `isInfixOf` s && not ('1' `elem` s))+ -- Bifoldable respects Override2: BL folds its list reversed+ , check "Override Bifold"(bifoldMap (: []) (: []) (Bc [1, 2] [3, 4 :: Int]) == [2, 1, 4, 3])+ -- Eq2 respects Override2: BL blind ⇒ all equal (same b-shape)+ , check "Override Eq2" (liftEq2 (==) (==) (Bc [1] [3]) (Bc [9, 9] [8, 8 :: Int]))+ -- Generic1 honours Override1: Generically1 Applicative zips (ZipList), not cartesian+ , check "Override Gen1Ap"(runZg (Zg [(+ 1), (* 10)] <*> Zg [5, 6]) == ([6, 60] :: [Int]))+ -- Ord2 honours Override2: BL's blind liftCompare ⇒ EQ regardless of contents+ , check "Override Ord2" (liftCompare2 compare compare (Bc [1] [3]) (Bc [9, 9] [8 :: Int]) == EQ+ && liftCompare compare (Lc [1]) (Lc [9 :: Int]) == EQ)+ -- Show2 honours Override2: each field renders through BL ⇒ "BL", not the list+ , check "Override Show2" (let s = liftShowsPrec2 showsPrec showList showsPrec showList 0 (Bc [1] [2 :: Int]) ""+ in "BL" `isInfixOf` s && not ('1' `elem` s))+ -- Read1 honours Override1: RL reverses on read, so the field comes back reversed+ , check "Override Read1" (case liftReadsPrec readsPrec readList 0 "Lr [1,2,3]" of+ ((v, _) : _) -> v == Lr [3, 2, 1 :: Int] ; _ -> False)+ -- Read2 honours Override2: both fields parsed through RL ⇒ both reversed+ , check "Override Read2" (case liftReadsPrec2 readsPrec readList readsPrec readList 0 "Br [1,2] [3,4]" of+ ((v, _) : _) -> v == Br [2, 1] [4, 3 :: Int] ; _ -> False)+ -- `_` (Keep) sugar lowered for Overriding1 too (identity reshape)+ , check "Override1 _ Keep" (fmap (+ (1 :: Int)) (Kp [1, 2, 3]) == Kp [2, 3, 4])+ -- field-keyed (name :=) Override1/Override2 — same surface as value Override+ , check "Override1 := name" (fmap (+ (1 :: Int)) (NK [1, 2, 3]) == NK [4, 3, 2])+ , check "Override2 := name" (bimap (+ (1 :: Int)) not (NK2 [1, 2] [True, False])+ == NK2 [3, 2] [True, False])+ -- Generic for a SUM type: from/to round-trips through the :+: structure+ , check "Generic sum rt"(all (\x -> to (from x) == x) [A, B 7, C 1 True, Rec 2 False])+ -- cross-validation: stock Generic Gen and plugin's Generic (Stock Gen)+ -- drive the same Generically algorithm to the same result+ , check "xval <>" (let x = Gen [1] [2]; y = Gen [3] [4]+ in viaGen x y == viaStockGen x y+ && viaGen x y == Gen [1,3] [2,4])+ , check "xval mempty" (memptyGen == memptyStockGen && memptyGen == Gen [] [])+ -- Rep (Stock T) ~R Rep T for a SUM type too+ , check "Rep ~R sum" (all (\x -> repCrossRoundtripS x == x) [GA, GB 5, GC 2 True])+ -- M1 metadata layers carry the right names (datatype, constructor, record)+ , check "Meta datatype" (datatypeName (from (MetaR 1)) == "MetaR")+ , check "Meta con" (conName (unM1 (from (MetaR 1))) == "MetaR")+ , check "Meta record" (conIsRecord (unM1 (from (MetaR 1))))+ -- Generic1: from1/to1 round-trip (Par1 / Rec1 / Rec0, sum + product)+ , check "Generic1 rt" (all (\x -> to1 (from1 x) == x)+ [G1 7 (1::Int) [2,3] [[4],[5,6]], G1' 9, G1 0 5 [] []])+ -- Rep (Stock T) is *representationally* equal to stock's Rep T (the M1+ -- metadata layers are newtypes): coerce across them and round-trip.+ , check "Rep ~R Rep T" (repCrossRoundtrip (Gen [1] [2]) == Gen [1] [2])+ -- infix constructors (fixity-aware Show/Read)+ , check "Show infix" (show e1 == norm (show e1'))+ , check "Show infix ()" (show e2 == norm (show e2'))+ , check "Read infix rt" (read (show e1) == e1 && read (show e2) == e2)+ -- Functor via Stock1, against a stock DeriveFunctor twin+ , check "Functor fmap" (fmap (+1) (Trio 1 2 [3,4]) == Trio 1 3 [4,5])+ , check "Functor vs twin"+ (show (fmap (*2) (Trio 1 2 [3])) == norm (show (fmap (*2) (Trio' 1 2 [3]))))+ , check "Functor <$" ((9 <$ Trio 1 2 [3,4]) == Trio 1 9 [9,9])+ -- Foldable via Stock1, against the stock twin+ -- Applicative with a constant field (Const-style, via Monoid)+ , check "Applicative pure" (pure 'z' == (Ap [] 'z' :: Ap Char))+ , check "Applicative <*>" ((Ap [1] (+1) <*> Ap [2] 10) == (Ap [1,2] 11 :: Ap Int))+ -- Override1: [] field → ZipList, so <*> zips (cartesian [] would give 4 elems)+ , check "Override1 zip <*>" (runZl (Zl [(+1),(*10)] <*> Zl [5,6]) == ([6,60] :: [Int]))+ , check "Override1 zip lA2" (runZl (liftA2 (+) (Zl [1,2,3]) (Zl [10,20,30])) == ([11,22,33] :: [Int]))+ , check "Override1 Foldable" (Data.Foldable.toList (Zl [4,5,6 :: Int]) == [4,5,6] && sum (Zl [1,2,3 :: Int]) == 6)+ , check "Foldable sum" (sum (Trio 9 1 [2,3,4]) == sum (Trio' 9 1 [2,3,4]))+ , check "Foldable toL" (Data.Foldable.toList (Trio 9 5 [6,7]) == [5,6,7])+ , check "Foldable len" (length (Trio 9 1 [2,3]) == 3)+ -- Eq1 / Ord1 via Stock1, tied to the (verified) Eq / Ord on Trio:+ -- liftEq (==) must agree with (==); liftCompare compare with compare.+ , check "Eq1 vs Eq" (let a = Trio 1 'x' "pq"; b = Trio 1 'x' "pq"; c = Trio 1 'y' "pr"+ in liftEq (==) a b == (a == b)+ && liftEq (==) a c == (a == c))+ , check "Eq1 param fn" (-- a custom relation on the parameter is threaded to+ -- both the bare field and the [a] field+ liftEq (\_ _ -> True) (Trio 1 'x' "pq") (Trio 1 'z' "rs")+ && not (liftEq (\_ _ -> False) (Trio 1 'x' "p") (Trio 1 'x' "p")))+ , check "Ord1 vs Ord" (let a = Trio 1 'x' "pq"; c = Trio 1 'y' "pr"+ in liftCompare compare a c == compare a c+ && liftCompare compare a a == EQ)+ -- the quantified superclass: from (Eq1 f, Eq a) alone we must get Eq (f a),+ -- and from (Ord1 f, Ord a) we must get Ord (f a) — f is abstract in the+ -- helpers below, so this can only resolve through the synthesized super.+ , check "Eq1 superclass" (eqViaEq1 (Trio 1 'x' "p") (Trio (1::Int) 'x' "p"))+ , check "Ord1 superclass" (cmpViaOrd1 (Trio 1 'x' "p") (Trio (1::Int) 'y' "p") == LT)+ -- Show1: showsPrec1 (which feeds liftShowsPrec the standard showsPrec/+ -- showList) must agree with the verified Show; a custom sp is threaded.+ , check "Show1 vs Show" (showsPrec1 0 (Trio (1::Int) 'x' "pq") "" == show (Trio (1::Int) 'x' "pq"))+ , check "Show1 twin" (showViaShow1 (Trio 9 (1::Int) [2,3]) == norm (show (Trio' 9 (1::Int) [2,3])))+ , check "Show1 param fn" (liftShowsPrec (\_ _ s -> 'Z':s) showList 0 (Trio (1::Int) 'x' "pq") ""+ == "Trio 1 Z \"pq\"")+ , check "Show1 paren" (showsPrec1 11 (Trio 9 (1::Int) [2]) "" == "(Trio 9 1 [2])")+ , check "Show1 record" (showsPrec1 0 (Recd (1::Int) [2,3]) "" == show (Recd (1::Int) [2,3]))+ -- Read1: readsPrec1 (fed the standard readsPrec/readList) must invert+ -- Show; the quantified Read superclass gives Read (f a) from f abstract.+ , check "Read1 rt" (let t = Trio (1::Int) (2::Int) [3,4]+ in case readsPrec1 0 (show t) of ((x,_):_) -> x == t; _ -> False)+ , check "Read1 super" (let t = Trio (9::Int) (1::Int) [2,3] in readViaRead1 (show t) == t)+ , check "Read1 record" (let t = Recd (1::Int) [2,3] in readViaRead1 (show t) == t)+ -- Contravariant via Stock1 (newtype + function/constant/sub-Pred fields)+ , check "Contra pred" (let p = contramap length (Pred even)+ in runPred p "abcd" && not (runPred p "abc"))+ , check "Contra law id" (let p = contramap id (Pred (> (3::Int)))+ in runPred p 4 && not (runPred p 3))+ , check "Contra mixed" (let Sel f _ (Pred q) =+ contramap (length :: [a] -> Int)+ (Sel (> (10::Int)) 0 (Pred (> 5)))+ in f "abcdefghijk" && not (f "abc")+ && q "abcdef" && not (q "abc"))+ , check "Contra 2-arg" (runFoo2 (contramap length (Foo2 (+))) "ab" "cde" == 5)+ -- variance through nested function arrows+ , check "Functor cps" (runCps (fmap (*2) (Cps (\k -> k 5))) id == 10)+ , check "Contra cps3" (forceCps3 (contramap (length :: String -> Int)+ (Cps3 (const 0)) :: Cps3 String) == ())+ -- Category via Stock2: pointwise id and composition over the fields+ , check "Category id" (runP2 (Cat.id :: P2 Int Int) 5 == (5 :: Int))+ , check "Category ." (runP2 ((P2 Refl (+1) :: P2 Int Int) Cat.. P2 Refl (*2)) 5 == (11 :: Int))+ -- Category with a constant (Sum Int) field, handled via Monoid (no Basic)+ , check "Category const"+ (let (s, f) = runLC ((LC 1 (+1) :: LC Int Int) Cat.. LC 2 (*2)) in s == 3 && f 5 == 11)+ -- Category via Overriding2: each field reshaped into a Category, then+ -- derived pointwise (Sum adds, String appends, Kleisli composes monadically)+ , check "Category Ov id"+ (let (i, s, f) = runFoo (Cat.id :: Foo Int Int) in i == 0 && s == "" && f 9 == Just 9)+ , check "Category Ov ."+ (let (i, s, f) = runFoo ((Foo 3 "x" (\n -> Just (n+1)) :: Foo Int Int)+ Cat.. Foo 4 "y" (\n -> Just (n*2)))+ in i == 7 && s == "xy" && f 5 == Just 11)+ -- Bifunctor / Bifoldable via Stock2+ , check "Bifunctor bi" (bimap (+(1::Int)) not (Bi 1 True) == Bi 2 False)+ , check "Bifunctor 1st" (first (+(1::Int)) (OnlyA 7 :: Bi Int Bool) == OnlyA 8)+ , check "Bifunctor 2nd" (second not (Bs True [False]) == (Bs False [True] :: Bi () Bool))+ , check "Bifunctor sup" (fmap not (Bs True [False,True]) == (Bs False [True,False] :: Bi () Bool))+ -- nested Either a b + [b] fields, via the n-ary self-application case+ , check "Bifunctor Either" (bimap (+(1::Int)) not (BiE (Left 5) [True])+ == (BiE (Left 6) [False] :: BiE Int Bool))+ , check "Bifunctor Either2" (bimap (+(1::Int)) not (BiE (Right True) [])+ == (BiE (Right False) [] :: BiE Int Bool))+ -- Eq2 / Ord2 via Stock2 (liftEq2 / liftCompare2 across both parameters)+ , check "Eq2" (liftEq2 (==) (==) (Bi (1::Int) True) (Bi 1 True)+ && not (liftEq2 (==) (==) (Bi (1::Int) True) (Bi 2 True)))+ , check "Show2" (let p :: Bi Int Bool -> String+ p x = liftShowsPrec2 showsPrec showList showsPrec showList 0 x ""+ in p (Bi 1 True) == show (Bi (1::Int) True)+ && p (Bs True [False,True]) == show (Bs True [False,True] :: Bi Int Bool))+ -- Read2 via Stock2: read back what show produced (ties to verified Show+Eq)+ , check "Read2" (let rd :: String -> Bi Int Bool+ rd s = case liftReadsPrec2 readsPrec readList readsPrec readList 0 s of+ [(v, "")] -> v+ _ -> error "Read2: no/ambiguous parse"+ in rd (show (Bi (1::Int) True)) == Bi 1 True+ && rd (show (Bs True [False,True] :: Bi Int Bool)) == Bs True [False,True])+ , check "Ord2" (liftCompare2 compare compare (Bi (1::Int) True) (Bi 1 False)+ == compare True False+ && liftCompare2 compare compare (Bi (1::Int) (2::Int)) (OnlyA 1) == LT)+ , check "Bifoldable" (bifoldMap (\a->[a]) (\b->[b]) (Bs 9 [1,2,3]) == [9,1,2,3::Int]+ && bifoldMap (\a->[a]) (\b->[b]) (Bi 1 2) == [1,2::Int])+ -- TestEquality / TestCoercion on the singleton GADT+ , check "TestEq same" (case testEquality TInt TInt of Just Refl -> True; _ -> False)+ , check "TestEq diff" (case testEquality TInt TBool of Nothing -> True; _ -> False)+ , check "TestEq refl" (case testEquality TBool TBool of+ Just Refl -> True && (True :: Bool); _ -> False)+ , check "TestEq use" (case testEquality TInt TInt of+ Just r -> castWith r (5 :: Int) == 5; Nothing -> False)+ , check "TestCo same" (case testCoercion TChar TChar of+ Just c -> coerceWith c 'x' == 'x'; Nothing -> False)+ , check "TestCo diff" (case testCoercion TInt TChar of Nothing -> True; _ -> False)+ -- same index, different constructors: compares the type, not the tag+ , check "TestEq sameIx" (case testEquality TZa TZb of Just Refl -> True; _ -> False)+ , check "TestEq sameIx'"(case testEquality TZb TZa of Just Refl -> True; _ -> False)+ , check "TestEq self" (case testEquality TZb TZb of Just Refl -> True; _ -> False)+ , check "TestEq mixIx" (case testEquality TZa TZc of Nothing -> True; _ -> False)+ , check "TestEq useZ" (case testEquality TZa TZb of+ Just r -> castWith r (7 :: Int) == 7; Nothing -> False)+ ]+ unless (and rs) exitFailure+ where+ xs = [C 1 True, A, B 1, Rec 9 True, A]+ e1 = Lit 1 :+: Lit 2 :*: Lit 3 ; e1' = Lit' 1 :+. Lit' 2 :*. Lit' 3+ e2 = (Lit 1 :+: Lit 2) :*: Lit 3; e2' = (Lit' 1 :+. Lit' 2) :*. Lit' 3+++
+ test/Twin.hs view
@@ -0,0 +1,48 @@+{-# OPTIONS_GHC -Wno-unused-top-binds #-}+-- | GHC-stock-derived twins with IDENTICAL constructor names, so the very same+-- input string can be fed to both the plugin's @Read@ and GHC's own derived+-- @Read@. This is the oracle for the Read-parity checks in "Main".+module Twin where++import GHC.Generics (Generic)++-- mixed: nullary / prefix / record+data Sum = A | B Int | C Int Bool | Rec { rf :: Int, rg :: Bool }+ deriving (Eq, Show, Read)++-- infix with distinct fixities (must match the plugin-side type exactly)+infixr 5 :+:+infixl 6 :*:+data Expr = Lit Int | Expr :+: Expr | Expr :*: Expr+ deriving (Eq, Show, Read)++-- parameterised, for the Read1 oracle (instantiated at a concrete type)+data Trio a = Trio Int a [a]+ deriving (Eq, Show, Read)++-- parameterised record, for the Read1 record path+data Recd a = Recd { rx :: a, ry :: [a] }+ deriving (Eq, Show, Read)++-- parameterised INFIX, for the Read1 ambiguous-order oracle+infixr 5 :+++data InfF a = ILit a | InfF a :++ InfF a+ deriving (Eq, Show, Read)++-- two parameters, for the Read2 oracle+data Bi a b = Bi a b | OnlyA a | Bs b [b] | Tag Int+ deriving (Eq, Show, Read)++-- two-parameter INFIX (non-recursive: Read2's flat classifier can't take a+-- self-applied field), a sanity oracle for Read2's infix-constructor path+infixr 5 :**+data InfB a b = IB a b | a :** b+ deriving (Eq, Show, Read)++-- GHC-stock Generic twins (identical names/fixity/strictness), so the whole+-- Rep below D1 can be statically compared against the via-Stock version.+infixr 7 :*:.+data MOp = Int :*:. Int deriving (Eq, Show, Generic)+data MStr = MStr ![Int] Int deriving (Eq, Show, Generic)+data MSum = MN | MP Int Bool | MR { mrf :: Int, mrg :: Bool }+ deriving (Eq, Show, Generic)