module HsDev.Tools.Ghc.Types (
TypedExpr(..), typedExpr, typedType,
moduleTypes, fileTypes,
setModuleTypes, inferTypes
) where
import Control.DeepSeq
import Control.Lens (over, view, set, each, preview, makeLenses, _Just)
import Control.Monad
import Control.Monad.IO.Class
import Data.Aeson
import Data.Generics
import Data.List (find)
import Data.Maybe
import Data.String (fromString)
import Data.Text (Text)
import System.Log.Simple (MonadLog(..), scope)
import GHC hiding (exprType, Module, moduleName)
import GHC.SYB.Utils (everythingStaged, Stage(TypeChecker))
import GhcPlugins (mkFunTys)
import CoreUtils as C
import Desugar (deSugarExpr)
import TcHsSyn (hsPatType)
import Outputable
import PprTyThing
import qualified Pretty
import System.Directory.Paths
import HsDev.Error
import HsDev.Symbols
import HsDev.Tools.Ghc.Worker as Ghc
import HsDev.Tools.Ghc.Compat
import HsDev.Tools.Types
import HsDev.Util
class HasType a where
getType :: GhcMonad m => a -> m (Maybe (SrcSpan, Type))
instance HasType (LHsExpr Id) where
getType e = do
env <- getSession
mbe <- liftIO $ liftM snd $ deSugarExpr env e
return $ do
ex <- mbe
return (getLoc e, C.exprType ex)
instance HasType (LHsBind Id) where
getType (L _ FunBind { fun_id = fid, fun_matches = m}) = return $ Just (getLoc fid, typ) where
typ = mkFunTys (mg_arg_tys m) (mg_res_ty m)
getType _ = return Nothing
instance HasType (LPat Id) where
getType (L spn pat) = return $ Just (spn, hsPatType pat)
locatedTypes :: Typeable a => TypecheckedSource -> [Located a]
locatedTypes = types' p where
types' :: Typeable r => (r -> Bool) -> GenericQ [r]
types' p' = everythingStaged TypeChecker (++) [] ([] `mkQ` (\x -> [x | p' x]))
p (L spn _) = isGoodSrcSpan spn
moduleTypes :: GhcMonad m => Path -> m [(SrcSpan, Type)]
moduleTypes fpath = do
fpath' <- liftIO $ canonicalize fpath
mg <- getModuleGraph
[m] <- liftIO $ flip filterM mg $ \m -> do
mfile <- traverse (liftIO . canonicalize) $ ml_hs_file (ms_location m)
return (Just (view path fpath') == mfile)
p <- parseModule m
tm <- typecheckModule p
let
ts = tm_typechecked_source tm
liftM (catMaybes . concat) $ sequence [
mapM getType (locatedTypes ts :: [LHsExpr Id]),
mapM getType (locatedTypes ts :: [LHsBind Id]),
mapM getType (locatedTypes ts :: [LPat Id])]
data TypedExpr = TypedExpr {
_typedExpr :: Maybe Text,
_typedType :: Text }
deriving (Eq, Ord, Read, Show)
makeLenses ''TypedExpr
instance NFData TypedExpr where
rnf (TypedExpr e t) = rnf e `seq` rnf t
instance ToJSON TypedExpr where
toJSON (TypedExpr e t) = object $ noNulls [
"expr" .= e,
"type" .= t]
instance FromJSON TypedExpr where
parseJSON = withObject "typed-expr" $ \v -> TypedExpr <$>
v .::? "expr" <*>
v .:: "type"
fileTypes :: (MonadLog m, GhcMonad m) => Module -> Maybe Text -> m [Note TypedExpr]
fileTypes m msrc = scope "types" $ case view (moduleId . moduleLocation) m of
FileModule file proj -> do
file' <- liftIO $ canonicalize file
cts <- maybe (liftIO $ readFileUtf8 (view path file')) return msrc
let
dir = fromMaybe
(sourceModuleRoot (view (moduleId . moduleName) m) file') $
preview (_Just . projectPath) proj
ex <- liftIO $ dirExists dir
(if ex then Ghc.withCurrentDirectory (view path dir) else id) $ do
target <- makeTarget (relPathTo dir file') msrc
loadTargets [target]
ts <- moduleTypes file'
df <- getSessionDynFlags
return $ map (setExpr cts . recalcTabs cts 8 . uncurry (toNote df)) ts
_ -> hsdevError $ ModuleNotSource (view (moduleId . moduleLocation) m)
where
toNote :: DynFlags -> SrcSpan -> Type -> Note Text
toNote df spn tp = Note {
_noteSource = noLocation,
_noteRegion = spanRegion spn,
_noteLevel = Nothing,
_note = fromString $ showType df tp }
setExpr :: Text -> Note Text -> Note TypedExpr
setExpr cts n = over note (TypedExpr (Just (regionStr (view noteRegion n) cts))) n
showType :: DynFlags -> Type -> String
showType df = renderStyle Pretty.OneLineMode 80 . withPprStyleDoc df (unqualStyle df) . pprTypeForUser
setModuleTypes :: [Note TypedExpr] -> Module -> Module
setModuleTypes ts = over (moduleScope . each . each) setType . over (moduleExports . each) setType where
setType :: Symbol -> Symbol
setType d = fromMaybe d $ do
pos <- view symbolPosition d
tnote <- find ((== pos) . view (noteRegion . regionFrom)) ts
return $ set (symbolInfo . functionType) (Just $ view (note . typedType) tnote) d
inferTypes :: (MonadLog m, GhcMonad m) => Module -> Maybe Text -> m Module
inferTypes m msrc = scope "infer" $ liftM (`setModuleTypes` m) $ fileTypes m msrc