th-context-0.13: Language/Haskell/TH/TypeGraph.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wall #-}
module Language.Haskell.TH.TypeGraph
( VertexStatus(..)
, TypeGraphEdges
, typeGraphEdges
, typeGraphVertices
, typeGraph
) where
#if __GLASGOW_HASKELL__ < 709
import Control.Applicative
import Data.Monoid (mempty)
#endif
import Control.Monad.State (execStateT, modify, MonadState(get), StateT)
import Control.Monad.Trans (lift)
import Data.Default (Default(def))
import Data.Graph (Graph, Vertex, graphFromEdges)
import Data.Map as Map (Map, keys, lookup, toList, update, alter)
import Data.Set as Set (insert, Set, empty, fromList, toList)
import Language.Haskell.Exts.Syntax ()
import Language.Haskell.TH -- (Con, Dec, nameBase, Type)
import Language.Haskell.TH.Desugar as DS (DsMonad)
import Language.Haskell.TH.Instances ()
import Language.Haskell.TH.Syntax (Quasi(..))
type TypeGraphEdges typ = Map typ (Set typ)
-- | When a VertexStatus value is associated with a Type it describes
-- alterations in the type graph from the usual default.
data VertexStatus typ
= Vertex -- ^ normal case
| NoVertex -- ^ exclude this type from the graph
| Sink -- ^ out degree zero - don't create any outgoing edges
| Divert typ -- ^ replace all outgoing edges with an edge to an alternate type
| Extra typ -- ^ add an extra outgoing edge to the given type
deriving Show
instance Default (VertexStatus typ) where
def = Vertex
-- | Return the set of types embedded in the given type. This is just
-- the nodes of the type graph. The type aliases are expanded by the
-- th-desugar package to make them suitable for use as map keys.
typeGraphVertices :: DsMonad m =>
(Type -> m (VertexStatus Type))
-> [Type]
-> m (Set Type)
typeGraphVertices augment types =
(Set.fromList . Map.keys) <$> typeGraphEdges augment types
typeGraphEdges
:: forall m. DsMonad m =>
(Type -> m (VertexStatus Type))
-- ^ This function is applied to every expanded type before
-- use, and the result is used instead. If it returns
-- NoVertex, no vertices or edges are added to the graph.
-- If it returns Sink no outgoing edges are added. The
-- current use case Substitute is to see if there is an
-- instance of class @View a b@ where @a@ is the type
-- passed to @doType@, and replace it with @b@, and use the
-- lens returned by @View's@ method to convert between @a@
-- and @b@ (i.e. to implement the edge in the type graph.)
-> [Type]
-> m (TypeGraphEdges Type)
typeGraphEdges augment types = do
execStateT (mapM_ doNode types) mempty
where
doNode :: Type -> StateT (TypeGraphEdges Type) m ()
doNode typ = do
mp <- get
status <- lift (augment typ)
case Map.lookup typ mp of
Just _ -> return ()
Nothing ->
case status of
NoVertex -> return ()
Sink -> addNode typ
(Divert typ') -> addNode typ >> addEdge typ typ' >> doNode typ'
(Extra typ') -> addNode typ >> doEdges typ >> addEdge typ typ' >> doNode typ'
Vertex -> addNode typ >> doEdges typ
addNode :: Type -> StateT (TypeGraphEdges Type) m ()
-- addNode a = expandType a >>= \ a' -> modify $ Map.insertWith (flip const) a' Set.empty
addNode a = modify $ Map.alter (maybe (Just Set.empty) Just) a
addEdge :: Type -> Type -> StateT (TypeGraphEdges Type) m ()
addEdge a b = modify $ Map.update (Just . Set.insert b) a
-- We know that the Type argument is actually fully expanded here.
doEdges :: Type -> StateT (TypeGraphEdges Type) m ()
doEdges typ@(ForallT _ _ typ') = addEdge typ typ' >> doNode typ'
doEdges typ@(AppT container element) =
addEdge typ container >>
addEdge typ element >>
doNode container >>
doNode element
-- Can this happen if typ is fully expanded?
doEdges typ@(ConT name) = do
info <- qReify name
case info of
TyConI dec -> doDec dec
_ -> return ()
where
doDec :: Dec -> StateT (TypeGraphEdges Type) m ()
doDec dec@(NewtypeD _ tname _ con _) = doCon tname dec con
doDec dec@(DataD _ tname _ cons _) = mapM_ (doCon tname dec) cons
doDec (TySynD _tname _tvars typ') = addEdge typ typ' >> doNode typ'
doDec _ = return ()
doCon :: Name -> Dec -> Con -> StateT (TypeGraphEdges Type) m ()
doCon tname dec (ForallC _ _ con) = doCon tname dec con
doCon tname dec (NormalC cname fields) = mapM_ (doField tname dec cname) (zip (map Left ([1..] :: [Int])) (map snd fields))
doCon tname dec (RecC cname fields) = mapM_ (doField tname dec cname) (map (\ (fname, _, typ') -> (Right fname, typ')) fields)
doCon tname dec (InfixC (_, lhs) cname (_, rhs)) = mapM_ (doField tname dec cname) [(Left 1, lhs), (Left 2, rhs)]
doField _tname _dec _cname (_fld, ftype) = addEdge typ ftype >> doNode ftype
doEdges _typ = return ({-trace ("Unrecognized type: " ++ pprint' typ)-} ())
-- | Build a graph from the result of typeGraphEdges, each edge goes
-- from a type to one of the types it contains. Thus, each edge
-- represents a primitive lens, and each path in the graph is a
-- composition of lenses.
typeGraph :: (DsMonad m, node ~ Type, key ~ Type) =>
(Type -> m (VertexStatus Type)) -> [Type] -> m (Graph, Vertex -> (node, key, [key]), key -> Maybe Vertex)
typeGraph augment types = do
typeGraphEdges augment types >>= return . graphFromEdges . triples
where
triples mp = map (\ (k, ks) -> (k, k, Set.toList ks)) $ Map.toList mp
{-
type FieldType = (Int, Either StrictType VarStrictType)
-- | The information required to extact a field value from a value.
-- We keep a stack of these as we traverse a declaration. Generally,
-- we only need the field names.
data StackElement = StackElement FieldType Con Dec deriving (Eq, Show, Data, Typeable)
class Monad m => HasStack m where
withStack :: ([StackElement] -> m a) -> m a -- Better name: askStack
push :: FieldType -> Con -> Dec -> m a -> m a -- Better name: localStack
instance (Quasi m, Monoid w) => HasStack (RWST [StackElement] w s m) where
withStack f = ask >>= f
push fld con dec action = local (\ stk -> StackElement fld con dec : stk) action
instance HasStack m => HasStack (StateT s m) where
withStack f = lift (withStack return) >>= f
push fld con dec action = get >>= \ st -> lift $ push fld con dec (evalStateT action st)
instance Quasi m => HasStack (ReaderT [StackElement] m) where
withStack f = ask >>= f
push fld con dec action = local (\ stk -> StackElement fld con dec : stk) action
instance (HasStack m, Monoid w) => HasStack (WriterT w m) where
withStack f = lift (withStack return) >>= f
push fld con dec action =
do (r, w') <- lift $ push fld con dec (runWriterT action)
tell w'
return r
prettyStack :: [StackElement] -> String
prettyStack = prettyStack' . reverse
where
prettyStack' :: [StackElement] -> String
prettyStack' [] = "(empty)"
prettyStack' (x : xs) = "[" ++ prettyElt x ++ prettyTail xs ++ "]"
prettyTail [] = ""
prettyTail (x : xs) = " → " ++ prettyElt x ++ prettyTail xs
prettyElt (StackElement fld con dec) =
foldDec prettyType (\ _ -> nameBase (decName dec)) dec ++ ":" ++
foldCon (\ name _ -> nameBase name) con ++ "." ++
prettyField fld
prettyType typ = foldTypeP nameBase
(\ t1 t2 -> "((" ++ prettyType t1 ++ ") (" ++ prettyType t2 ++ "))")
("(" ++ show typ ++ ")")
typ
type StackT m = ReaderT [StackElement] m
execStackT :: Monad m => StackT m a -> m a
execStackT action = runReaderT action []
-- | Combine a decFn and a primFn to make a nameFn in the Quasi monad.
-- This is used to build the first argument to the foldType function
-- when we need to know whether the name refers to a declared or a
-- primitive type.
foldName :: Quasi m =>
(Dec -> m r)
-> (Name -> Int -> Bool -> m r)
-> (Info -> m r)
-> Name -> m r
foldName decFn primFn otherFn name = do
info <- qReify name
case info of
(TyConI dec) ->
decFn dec
(PrimTyConI a b c) -> primFn a b c
_ -> otherFn info
-- | Dispatch on the different constructors of the Dec type.
foldDec :: Monad m =>
(Type -> m r)
-> ([Con] -> m r)
-> Dec -> m r
foldDec typeFn shapeFn dec =
case dec of
TySynD _name _ typ -> typeFn typ
NewtypeD _ _ _ con _ -> shapeFn [con]
DataD _ _ _ cons _ -> shapeFn cons
_ -> error $ "foldDec - unexpected: " ++ show dec
decName :: Dec -> Name
decName (NewtypeD _ name _ _ _) = name
decName (DataD _ name _ _ _) = name
decName (TySynD name _ _) = name
decName x = error $ "decName - unimplemented: " ++ show x
-- | Deconstruct a constructor
foldCon :: (Name -> [FieldType] -> r) -> Con -> r
foldCon fldFn (NormalC name ts) = fldFn name $ zip [1..] (map Left ts)
foldCon fldFn (RecC name ts) = fldFn name (zip [1..] (map Right ts))
foldCon fldFn (InfixC t1 name t2) = fldFn name [(1, Left t1), (2, Left t2)]
foldCon fldFn (ForallC _ _ con) = foldCon fldFn con
prettyField :: FieldType -> String
prettyField fld = maybe (show (fPos fld)) nameBase (fName fld)
-- | Pure version of foldType.
foldTypeP :: (Name -> r) -> (Type -> Type -> r) -> r -> Type -> r
foldTypeP nfn afn ofn typ = runIdentity $ foldType (\ n -> Identity $ nfn n) (\ t1 t2 -> Identity $ afn t1 t2) (Identity ofn) typ
fPos :: FieldType -> Int
fPos (n, _) = n
fName :: FieldType -> Maybe Name
fName (_, (Left _)) = Nothing
fName (_, (Right (name, _, _))) = Just name
-- | Dispatch on the constructors of type Type. This ignores the
-- "ForallT" constructor, it just uses the embeded Type field.
foldType :: Monad m => (Name -> m r) -> (Type -> Type -> m r) -> m r -> Type -> m r
foldType nfn afn ofn (ForallT _ _ typ) = foldType nfn afn ofn typ
foldType nfn _ _ (ConT name) = nfn name
foldType _ afn _ (AppT t1 t2) = afn t1 t2
foldType _ _ ofn _ = ofn
-}
{-
adjacentTypes :: DsMonad m => Type -> m (Type, [Type])
adjacentTypes (ForallT _ _ typ) = adjacentTypes typ
adjacentTypes (AppT t1 t2) = [t1, t2]
adjacentTypes t@(ConT _) = [t]
adjacentTypes t@(VarT _) = [t]
adjacentTypes _ = []
-}