packages feed

hls-tactics-plugin-0.5.1.0: src/Ide/Plugin/Tactic/Context.hs

{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase       #-}

module Ide.Plugin.Tactic.Context where

import           Bag
import           Control.Arrow
import           Control.Monad.Reader
import           Data.List
import           Data.Map (Map)
import qualified Data.Map as M
import           Data.Maybe (mapMaybe)
import           Data.Set (Set)
import qualified Data.Set as S
import           Development.IDE.GHC.Compat
import           Ide.Plugin.Tactic.GHC (tacticsThetaTy)
import           Ide.Plugin.Tactic.Machinery (methodHypothesis)
import           Ide.Plugin.Tactic.Types
import           OccName
import           TcRnTypes
import           TcType (substTy, tcSplitSigmaTy)
import           Unify (tcUnifyTy)


mkContext :: [(OccName, CType)] -> TcGblEnv -> Context
mkContext locals tcg = Context
  { ctxDefiningFuncs = locals
  , ctxModuleFuncs = fmap splitId
                   . (getFunBindId =<<)
                   . fmap unLoc
                   . bagToList
                   $ tcg_binds tcg
  }


------------------------------------------------------------------------------
-- | Find all of the class methods that exist from the givens in the context.
contextMethodHypothesis :: Context -> Map OccName (HyInfo CType)
contextMethodHypothesis ctx
  = M.fromList
  . excludeForbiddenMethods
  . join
  . concatMap
      ( mapMaybe methodHypothesis
      . tacticsThetaTy
      . unCType
      )
  . mapMaybe (definedThetaType ctx)
  . fmap fst
  $ ctxDefiningFuncs ctx


------------------------------------------------------------------------------
-- | Many operations are defined in typeclasses for performance reasons, rather
-- than being a true part of the class. This function filters out those, in
-- order to keep our hypothesis space small.
excludeForbiddenMethods :: [(OccName, a)] -> [(OccName, a)]
excludeForbiddenMethods = filter (not . flip S.member forbiddenMethods  . fst)
  where
    forbiddenMethods :: Set OccName
    forbiddenMethods = S.map mkVarOcc $ S.fromList
      [ -- monadfail
        "fail"
      ]


------------------------------------------------------------------------------
-- | Given the name of a function that exists in 'ctxDefiningFuncs', get its
-- theta type.
definedThetaType :: Context -> OccName -> Maybe CType
definedThetaType ctx name = do
  (_, CType mono) <- find ((== name) . fst) $ ctxDefiningFuncs ctx
  (_, CType poly) <- find ((== name) . fst) $ ctxModuleFuncs ctx
  let (_, _, poly') = tcSplitSigmaTy poly
  subst <- tcUnifyTy poly' mono
  pure $ CType $ substTy subst $ snd $ splitForAllTys poly


splitId :: Id -> (OccName, CType)
splitId = occName &&& CType . idType


getFunBindId :: HsBindLR GhcTc GhcTc -> [Id]
getFunBindId (AbsBinds _ _ _ abes _ _ _)
  = abes >>= \case
      ABE _ poly _ _ _ -> pure poly
      _ -> []
getFunBindId _ = []


getCurrentDefinitions :: MonadReader Context m => m [(OccName, CType)]
getCurrentDefinitions = asks $ ctxDefiningFuncs

getModuleHypothesis :: MonadReader Context m => m [(OccName, CType)]
getModuleHypothesis = asks ctxModuleFuncs