Skip to content
This repository was archived by the owner on Oct 7, 2020. It is now read-only.

Typemap reimplementation #1186

Merged
merged 16 commits into from
May 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions hie-plugin-api/Haskell/Ide/Engine/ArtifactMap.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import GHC (TypecheckedModule)
import qualified SrcLoc as GHC
import qualified Var
import qualified GhcMod.Gap as GM
import GhcMod.SrcUtils

import Language.Haskell.LSP.Types

Expand All @@ -33,15 +32,6 @@ genIntervalMap ts = foldr go IM.empty ts

-- ---------------------------------------------------------------------

genTypeMap :: GHC.GhcMonad m => TypecheckedModule -> m TypeMap
genTypeMap tm = do
ts <- collectAllSpansTypes True tm
return $ foldr go IM.empty ts
where
go (GHC.RealSrcSpan spn, typ) im =
IM.insert (rspToInt spn) typ im
go _ im = im

-- | Generates a LocMap from a TypecheckedModule,
-- which allows fast queries for all the symbols
-- located at a particular point in the source
Expand Down
113 changes: 113 additions & 0 deletions hie-plugin-api/Haskell/Ide/Engine/Compat.hs
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ViewPatterns #-}
module Haskell.Ide.Engine.Compat where

import qualified GHC
import qualified Type
import qualified TcHsSyn
import qualified TysWiredIn
import qualified Var

#if MIN_VERSION_filepath(1,4,2)
#else
import Data.List
Expand All @@ -27,3 +35,108 @@ isExtensionOf :: String -> FilePath -> Bool
isExtensionOf ext@('.':_) = isSuffixOf ext . takeExtensions
isExtensionOf ext = isSuffixOf ('.':ext) . takeExtensions
#endif


#if MIN_VERSION_ghc(8, 4, 0)
type GhcTc = GHC.GhcTc
#else
type GhcTc = GHC.Id
#endif

pattern HsOverLitType :: Type.Type -> GHC.HsExpr GhcTc
pattern HsOverLitType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.HsOverLit _ (GHC.overLitType -> t)
#elif MIN_VERSION_ghc(8, 4, 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can just have the else clause here I think.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I wanted to ask that anyway, do we prefer easy to read, e.g. always three clauses although they often completely overlap, or conciseness when it comes to CPP?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer not having the duplication and just having two clauses when possible.

GHC.HsOverLit (GHC.overLitType -> t)
#else
GHC.HsOverLit (GHC.overLitType -> t)
#endif

pattern HsLitType :: Type.Type -> GHC.HsExpr GhcTc
pattern HsLitType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.HsLit _ (TcHsSyn.hsLitType -> t)
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.HsLit (TcHsSyn.hsLitType -> t)
#else
GHC.HsLit (TcHsSyn.hsLitType -> t)
#endif

pattern HsLamType :: Type.Type -> GHC.HsExpr GhcTc
pattern HsLamType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.HsLam _ ((\(GHC.MG { GHC.mg_ext = groupTy }) -> matchGroupType groupTy) -> t)
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.HsLam (\GHC.MG { GHC.mg_res_ty = res, GHC.mg_arg_tys = args } -> Type.mkFunTys args res -> t)
#else
GHC.HsLam (\GHC.MG { GHC.mg_res_ty = res, GHC.mg_arg_tys = args } -> Type.mkFunTys args res -> t)
#endif

pattern HsLamCaseType :: Type.Type -> GHC.HsExpr GhcTc
pattern HsLamCaseType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.HsLamCase _ ((\(GHC.MG { GHC.mg_ext = groupTy }) -> matchGroupType groupTy) -> t)
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.HsLamCase (\GHC.MG { GHC.mg_res_ty = res, GHC.mg_arg_tys = args } -> Type.mkFunTys args res -> t)
#else
GHC.HsLamCase (\GHC.MG { GHC.mg_res_ty = res, GHC.mg_arg_tys = args } -> Type.mkFunTys args res -> t)
#endif

pattern HsCaseType :: Type.Type -> GHC.HsExpr GhcTc
pattern HsCaseType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.HsCase _ _ ((\(GHC.MG { GHC.mg_ext = groupTy }) -> matchGroupType groupTy) -> t)
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.HsCase _ (\GHC.MG { GHC.mg_res_ty = res, GHC.mg_arg_tys = args } -> Type.mkFunTys args res -> t)
#else
GHC.HsCase _ (\GHC.MG { GHC.mg_res_ty = res, GHC.mg_arg_tys = args } -> Type.mkFunTys args res -> t)
#endif

pattern ExplicitListType :: Type.Type -> GHC.HsExpr GhcTc
pattern ExplicitListType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.ExplicitList (TysWiredIn.mkListTy -> t) _ _
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.ExplicitList (TysWiredIn.mkListTy -> t) _ _
#else
GHC.ExplicitList (TysWiredIn.mkListTy -> t) _ _
#endif

pattern ExplicitSumType :: Type.Type -> GHC.HsExpr GhcTc
pattern ExplicitSumType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.ExplicitSum (TysWiredIn.mkSumTy -> t) _ _ _
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.ExplicitSum _ _ _ (TysWiredIn.mkSumTy -> t)
#else
GHC.ExplicitSum _ _ _ (TysWiredIn.mkSumTy -> t)
#endif


pattern HsMultiIfType :: Type.Type -> GHC.HsExpr GhcTc
pattern HsMultiIfType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.HsMultiIf t _
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.HsMultiIf t _
#else
GHC.HsMultiIf t _
#endif

pattern FunBindType :: Type.Type -> GHC.HsBindLR GhcTc GhcTc
pattern FunBindType t <-
#if MIN_VERSION_ghc(8, 6, 0)
GHC.FunBind _ (GHC.L _ (Var.varType -> t)) _ _ _
#elif MIN_VERSION_ghc(8, 4, 0)
GHC.FunBind (GHC.L _ (Var.varType -> t)) _ _ _ _
#else
GHC.FunBind (GHC.L _ (Var.varType -> t)) _ _ _ _
#endif


#if MIN_VERSION_ghc(8, 6, 0)
matchGroupType :: GHC.MatchGroupTc -> GHC.Type
matchGroupType (GHC.MatchGroupTc args res) = Type.mkFunTys args res
#endif

1 change: 1 addition & 0 deletions hie-plugin-api/Haskell/Ide/Engine/ModuleCache.hs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import qualified GhcMod.Utils as GM
import qualified GHC as GHC

import Haskell.Ide.Engine.ArtifactMap
import Haskell.Ide.Engine.TypeMap
import Haskell.Ide.Engine.GhcModuleCache
import Haskell.Ide.Engine.MultiThreadState
import Haskell.Ide.Engine.PluginsIdeMonads
Expand Down
152 changes: 152 additions & 0 deletions hie-plugin-api/Haskell/Ide/Engine/TypeMap.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE LambdaCase #-}
module Haskell.Ide.Engine.TypeMap where

import qualified Data.IntervalMap.FingerTree as IM

import qualified GHC
import GHC ( TypecheckedModule )

import Data.Data as Data
import Control.Monad.IO.Class
import Data.Maybe
import qualified TcHsSyn
import qualified CoreUtils
import qualified Type
import qualified Desugar
import Haskell.Ide.Engine.Compat

import Haskell.Ide.Engine.ArtifactMap

-- | Generate a mapping from an Interval to types.
-- Intervals may overlap and return more specific results.
genTypeMap :: GHC.GhcMonad m => TypecheckedModule -> m TypeMap
genTypeMap tm = do
let typecheckedSource = GHC.tm_typechecked_source tm
hs_env <- GHC.getSession
liftIO $ types hs_env typecheckedSource


everythingInTypecheckedSourceM
:: Data x => (forall a . Data a => a -> IO TypeMap) -> x -> IO TypeMap
everythingInTypecheckedSourceM = everythingButTypeM @GHC.Id


-- | Obtain details map for types.
types :: GHC.HscEnv -> GHC.TypecheckedSource -> IO TypeMap
types hs_env = everythingInTypecheckedSourceM (ty `combineM` fun `combineM` funBind)
where
ty :: forall a . Data a => a -> IO TypeMap
ty term = case cast term of
(Just lhsExprGhc@(GHC.L (GHC.RealSrcSpan spn) _)) ->
getType hs_env lhsExprGhc >>= \case
Nothing -> return IM.empty
Just (_, typ) -> return (IM.singleton (rspToInt spn) typ)
_ -> return IM.empty

fun :: forall a . Data a => a -> IO TypeMap
fun term = case cast term of
(Just (GHC.L (GHC.RealSrcSpan spn) hsPatType)) ->
return (IM.singleton (rspToInt spn) (TcHsSyn.hsPatType hsPatType))
_ -> return IM.empty

funBind :: forall a . Data a => a -> IO TypeMap
funBind term = case cast term of
(Just (GHC.L (GHC.RealSrcSpan spn) (FunBindType t))) ->
return (IM.singleton (rspToInt spn) t)
_ -> return IM.empty

-- | Combine two queries into one using alternative combinator.
combineM
:: (forall a . Data a => a -> IO TypeMap)
-> (forall a . Data a => a -> IO TypeMap)
-> (forall a . Data a => a -> IO TypeMap)
combineM f g x = do
a <- f x
b <- g x
return (a `IM.union` b)

-- | Variation of "everything" that does not recurse into children of type t
-- requires AllowAmbiguousTypes
everythingButTypeM
:: forall t
. (Typeable t)
=> (forall a . Data a => a -> IO TypeMap)
-> (forall a . Data a => a -> IO TypeMap)
everythingButTypeM f = everythingButM $ (,) <$> f <*> isType @t

-- | Returns true if a == t.
-- requires AllowAmbiguousTypes
isType :: forall a b . (Typeable a, Typeable b) => b -> Bool
isType _ = isJust $ eqT @a @b

-- | Variation of "everything" with an added stop condition
-- Just like 'everything', this is stolen from SYB package.
everythingButM
:: (forall a . Data a => a -> (IO TypeMap, Bool))
-> (forall a . Data a => a -> IO TypeMap)
everythingButM f x = do
let (v, stop) = f x
if stop
then v
else Data.gmapQr
(\e acc -> do
e' <- e
a <- acc
return (e' `IM.union` a)
)
v
(everythingButM f)
x

-- | Attempts to get the type for expressions in a lazy and cost saving way.
-- Avoids costly desugaring of Expressions and only obtains the type at the leaf of an expression.
--
-- Implementation is taken from: HieAst.hs<https://gitlab.haskell.org/ghc/ghc/blob/1f5cc9dc8aeeafa439d6d12c3c4565ada524b926/compiler/hieFile/HieAst.hs>
-- Slightly adapted to work for the supported GHC versions 8.2.1 - 8.6.4
--
-- See #16233<https://gitlab.haskell.org/ghc/ghc/issues/16233>
getType
:: GHC.HscEnv -> GHC.LHsExpr GhcTc -> IO (Maybe (GHC.SrcSpan, Type.Type))
getType hs_env e@(GHC.L spn e') =
-- Some expression forms have their type immediately available
let
tyOpt = case e' of
HsOverLitType t -> Just t
HsLitType t -> Just t
HsLamType t -> Just t
HsLamCaseType t -> Just t
HsCaseType t -> Just t
ExplicitListType t -> Just t
ExplicitSumType t -> Just t
HsMultiIfType t -> Just t

_ -> Nothing
in case tyOpt of
Just t -> return $ Just (spn ,t)
Nothing
| skipDesugaring e' -> pure Nothing
| otherwise -> do
(_, mbe) <- Desugar.deSugarExpr hs_env e
let res = (spn, ) . CoreUtils.exprType <$> mbe
pure res
where
-- | Skip desugaring of these expressions for performance reasons.
--
-- See impact on Haddock output (esp. missing type annotations or links)
-- before marking more things here as 'False'. See impact on Haddock
-- performance before marking more things as 'True'.
skipDesugaring :: GHC.HsExpr a -> Bool
skipDesugaring expression = case expression of
GHC.HsVar{} -> False
GHC.HsUnboundVar{} -> False
GHC.HsConLikeOut{} -> False
GHC.HsRecFld{} -> False
GHC.HsOverLabel{} -> False
GHC.HsIPVar{} -> False
GHC.HsWrap{} -> False
_ -> True
1 change: 1 addition & 0 deletions hie-plugin-api/hie-plugin-api.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ library
Haskell.Ide.Engine.MultiThreadState
Haskell.Ide.Engine.PluginsIdeMonads
Haskell.Ide.Engine.PluginUtils
Haskell.Ide.Engine.TypeMap
build-depends: base >= 4.9 && < 5
, Diff
, aeson
Expand Down
33 changes: 33 additions & 0 deletions test/testdata/Types.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
module Types where

import Control.Applicative

foo :: Maybe Int -> Int
foo (Just x) = x
foo Nothing = 0

bar :: Maybe Int -> Int
bar x = case x of
Just y -> y + 1
Nothing -> 0

maybeMonad :: Maybe Int -> Maybe Int
maybeMonad x = do
y <- x
let z = return (y + 10)
b <- z
return (b + y)

funcTest :: (a -> a) -> a -> a
funcTest f a = f a

compTest :: (b -> c) -> (a -> b) -> a -> c
compTest f g = let h = f . g in h

monadStuff :: (a -> b) -> IO a -> IO b
monadStuff f action = f <$> action

data Test
= TestC Int
| TestM String
deriving (Show, Eq, Ord)
Loading