packages feed

hls-tactics-plugin-1.2.0.0: src/Wingman/CaseSplit.hs

module Wingman.CaseSplit
  ( mkFirstAgda
  , iterateSplit
  , splitToDecl
  ) where

import           Data.Bool (bool)
import           Data.Data
import           Data.Generics
import           Data.Set (Set)
import qualified Data.Set as S
import           Development.IDE.GHC.Compat
import           GHC.Exts (IsString (fromString))
import           GHC.SourceGen (funBinds, match, wildP)
import           OccName
import           Wingman.GHC
import           Wingman.Types



------------------------------------------------------------------------------
-- | Construct an 'AgdaMatch' from patterns in scope (should be the LHS of the
-- match) and a body.
mkFirstAgda :: [Pat GhcPs] -> HsExpr GhcPs -> AgdaMatch
mkFirstAgda pats (Lambda pats' body) = mkFirstAgda (pats <> pats') body
mkFirstAgda pats body                = AgdaMatch pats body


------------------------------------------------------------------------------
-- | Transform an 'AgdaMatch' whose body is a case over a bound pattern, by
-- splitting it into multiple matches: one for each alternative of the case.
agdaSplit :: AgdaMatch -> [AgdaMatch]
agdaSplit (AgdaMatch pats (Case (HsVar _ (L _ var)) matches)) = do
  (pat, body) <- matches
  -- TODO(sandy): use an at pattern if necessary
  pure $ AgdaMatch (rewriteVarPat var pat pats) $ unLoc body
agdaSplit x = [x]


------------------------------------------------------------------------------
-- | Replace unused bound patterns with wild patterns.
wildify :: AgdaMatch -> AgdaMatch
wildify (AgdaMatch pats body) =
  let make_wild = bool id (wildifyT (allOccNames body)) $ not $ containsHole body
   in AgdaMatch (make_wild pats) body


------------------------------------------------------------------------------
-- | Helper function for 'wildify'.
wildifyT :: Data a => Set OccName -> a -> a
wildifyT (S.map occNameString -> used) = everywhere $ mkT $ \case
  VarPat _ (L _ var) | S.notMember (occNameString $ occName var) used -> wildP
  (x :: Pat GhcPs)                                                    -> x


------------------------------------------------------------------------------
-- | Replace a 'VarPat' with the given @'Pat' GhcPs@.
rewriteVarPat :: Data a => RdrName -> Pat GhcPs -> a -> a
rewriteVarPat name rep = everywhere $
  mkT (\case
    VarPat _ (L _ var) | eqRdrName name var -> rep
    (x :: Pat GhcPs)                        -> x
      )
  `extT` \case
    HsRecField lbl _ True
      | eqRdrName name $ unLoc $ rdrNameFieldOcc $ unLoc lbl
          -> HsRecField lbl (toPatCompat rep) False
    (x :: HsRecField' (FieldOcc GhcPs) (PatCompat GhcPs)) -> x



------------------------------------------------------------------------------
-- | Construct an 'HsDecl' from a set of 'AgdaMatch'es.
splitToDecl
    :: OccName  -- ^ The name of the function
    -> [AgdaMatch]
    -> LHsDecl GhcPs
splitToDecl name ams = noLoc $ funBinds (fromString . occNameString . occName $ name) $ do
  AgdaMatch pats body <- ams
  pure $ match pats body


------------------------------------------------------------------------------
-- | Sometimes 'agdaSplit' exposes another opportunity to do 'agdaSplit'. This
-- function runs it a few times, hoping it will find a fixpoint.
iterateSplit :: AgdaMatch -> [AgdaMatch]
iterateSplit am =
  let iterated = iterate (agdaSplit =<<) $ pure am
   in fmap wildify . head . drop 5 $ iterated