large-anon-0.3.3: src/Data/Record/Anon/Internal/Plugin/TC/Parsing.hs
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
-- | Generic parsing infrastructure
--
-- TODO: Perhaps we could move this (or some form of this) to ghc-tcplugin-api?
-- (The @typelet@ package could then use it, too.)
module Data.Record.Anon.Internal.Plugin.TC.Parsing (
-- * Basic infrastructure
ParseResult(..)
, parseAll
, parseAll'
, withOrig
-- * Parsers for specific (but not @large-anon@ specific) constructs
, parseConstraint
, parseConstraint'
, parseCons
, parseNil
, parseInjTyConApp
) where
import Data.Bifunctor
import Data.Foldable (toList)
import Data.Void
import GHC.Stack
import Data.Record.Anon.Internal.Plugin.TC.GhcTcPluginAPI
{-------------------------------------------------------------------------------
Basic infrastructure
-------------------------------------------------------------------------------}
data ParseResult e a =
-- | Parse successful
ParseOk a
-- | Different constraint than we're looking for (does not imply an error)
| ParseNoMatch
-- | Constraint of the shape we're looking for, but something is wrong
| ParseError e
deriving (Functor)
instance Bifunctor ParseResult where
bimap _ g (ParseOk a) = ParseOk (g a)
bimap _ _ ParseNoMatch = ParseNoMatch
bimap f _ (ParseError e) = ParseError (f e)
-- | Apply parser to each value in turn, bailing at the first error
parseAll :: forall e a b. (a -> ParseResult e b) -> [a] -> Either e [b]
parseAll f = go []
where
go :: [b] -> [a] -> Either e [b]
go acc [] = Right (reverse acc)
go acc (a:as) = case f a of
ParseOk b -> go (b:acc) as
ParseNoMatch -> go acc as
ParseError e -> Left e
-- | Variation on 'parseAll' which rules out the error case
parseAll' :: (a -> ParseResult Void b) -> [a] -> [b]
parseAll' f = aux . parseAll f
where
aux :: Either Void [b] -> [b]
aux (Left v) = absurd v
aux (Right bs) = bs
-- | Bundle the parse result with the original value
withOrig :: (a -> ParseResult e b) -> (a -> ParseResult e (a, b))
withOrig f x = fmap (x, ) $ f x
{-------------------------------------------------------------------------------
Parsers for specific (but not @large-anon@ specific) constructs
-------------------------------------------------------------------------------}
-- | Generic constraint parser
--
-- TODO: If we add some parsing infra to ghc-tcplugin-api, maybe a (form of)
-- this function could live there too.
parseConstraint ::
HasCallStack
=> (Class -> [Type] -> Maybe a) -- ^ Do we want to try and match against this?
-> (a -> Maybe b) -- ^ Parser for the class arguments
-> Ct -- ^ Constraint to parse
-> ParseResult e (GenLocated CtLoc b)
parseConstraint p f ct = fmap (L $ ctLoc ct) $
-- TODO: classify up to equalities..?
case classifyPredType (ctPred ct) of
ClassPred cls args | Just a <- p cls args ->
case f a of
Just parsed ->
ParseOk parsed
Nothing ->
panic $ concat [
"Unexpected "
, showSDocUnsafe (ppr cls)
, " constraint with arguments:\n"
, unlines (map (showSDocUnsafe . ppr) args)
, "\nat\n"
, prettyCallStack callStack
]
_otherwise ->
ParseNoMatch
-- | Specialization of 'parseConstraint', just checking the class name
parseConstraint' ::
HasCallStack
=> Class -- ^ Predicate we want to match against
-> ([Type] -> Maybe a) -- ^ Parser for the class arguments
-> Ct -- ^ Constraint to parse
-> ParseResult e (GenLocated CtLoc a)
parseConstraint' cls = parseConstraint p
where
p :: Class -> [Type] -> Maybe [Type]
p cls' args = if cls == cls' then Just args else Nothing
-- | Parse @x ': xs == (':) x xs == ((':) x) xs@
parseCons :: TyConSubst -> Type -> Maybe (Type, Type)
parseCons tcs t = do
args <- parseInjTyConApp tcs promotedConsDataCon t
case args of
[_k, x, xs] -> Just (x, xs)
_otherwise -> Nothing
-- | Parse @'[]@
parseNil :: TyConSubst -> Type -> Maybe ()
parseNil tcs t = do
args <- parseInjTyConApp tcs promotedNilDataCon t
case args of
[_k] -> Just ()
_otherwise -> Nothing
-- | Parse application of an injective type constructor
parseInjTyConApp :: TyConSubst -> TyCon -> Type -> Maybe [Type]
parseInjTyConApp tcs tyCon t = do
splits <- splitTyConApp_upTo tcs t
-- At this point we might have multiple matches
--
-- > t ~ TyCon1 args1
-- > t ~ TyCon1 args1'
-- > t ~ TyCon2 args2
-- > ..
--
-- We are only interested in the equalities with @tyCon@ at the head, but
-- this may still leave us with multiple equalities
--
-- > t ~ tyCon args1
-- > t ~ tyCon args1'
--
-- When this is the case, however, by injectivity of 'tyCon' we know that
-- @args1 ~ args1'@, so we can just return /any/ of the matches; we will
-- return the first.
lookup tyCon (toList splits)