hls-tactics-plugin-1.0.0.0: src/Ide/Plugin/Tactic/LanguageServer.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Ide.Plugin.Tactic.LanguageServer where
import Control.Arrow
import Control.Monad
import Control.Monad.Trans.Maybe
import Data.Aeson (Value (Object), fromJSON)
import Data.Aeson.Types (Result (Error, Success))
import Data.Coerce
import Data.Functor ((<&>))
import Data.Generics.Aliases (mkQ)
import Data.Generics.Schemes (everything)
import Data.Map (Map)
import qualified Data.Map as M
import Data.Maybe
import Data.Monoid
import qualified Data.Set as S
import qualified Data.Text as T
import Data.Traversable
import Development.IDE (ShakeExtras,
getPluginConfig)
import Development.IDE.Core.PositionMapping
import Development.IDE.Core.RuleTypes
import Development.IDE.Core.Service (runAction)
import Development.IDE.Core.Shake (IdeState (..),
useWithStale)
import Development.IDE.GHC.Compat
import Development.IDE.GHC.Error (realSrcSpanToRange)
import Development.IDE.Spans.LocalBindings (Bindings,
getDefiningBindings)
import Development.Shake (Action, RuleResult)
import Development.Shake.Classes
import qualified FastString
import Ide.Plugin.Config (PluginConfig (plcConfig))
import qualified Ide.Plugin.Config as Plugin
import Ide.Plugin.Tactic.Context
import Ide.Plugin.Tactic.FeatureSet
import Ide.Plugin.Tactic.GHC
import Ide.Plugin.Tactic.Judgements
import Ide.Plugin.Tactic.Range
import Ide.Plugin.Tactic.TestTypes (TacticCommand,
cfg_feature_set)
import Ide.Plugin.Tactic.Types
import Language.LSP.Server (MonadLsp)
import Language.LSP.Types
import OccName
import Prelude hiding (span)
import SrcLoc (containsSpan)
import TcRnTypes (tcg_binds)
tacticDesc :: T.Text -> T.Text
tacticDesc name = "fill the hole using the " <> name <> " tactic"
------------------------------------------------------------------------------
-- | The name of the command for the LS.
tcCommandName :: TacticCommand -> T.Text
tcCommandName = T.pack . show
runIde :: IdeState -> Action a -> IO a
runIde state = runAction "tactic" state
runStaleIde
:: forall a r
. ( r ~ RuleResult a
, Eq a , Hashable a , Binary a , Show a , Typeable a , NFData a
, Show r, Typeable r, NFData r
)
=> IdeState
-> NormalizedFilePath
-> a
-> MaybeT IO (r, PositionMapping)
runStaleIde state nfp a = MaybeT $ runIde state $ useWithStale a nfp
------------------------------------------------------------------------------
-- | Get the current feature set from the plugin config.
getFeatureSet :: MonadLsp Plugin.Config m => ShakeExtras -> m FeatureSet
getFeatureSet extras = do
pcfg <- getPluginConfig extras "tactics"
pure $ case fromJSON $ Object $ plcConfig pcfg of
Success cfg -> cfg_feature_set cfg
Error _ -> defaultFeatures
getIdeDynflags
:: IdeState
-> NormalizedFilePath
-> MaybeT IO DynFlags
getIdeDynflags state nfp = do
-- Ok to use the stale 'ModIface', since all we need is its 'DynFlags'
-- which don't change very often.
((modsum,_), _) <- runStaleIde state nfp GetModSummaryWithoutTimestamps
pure $ ms_hspp_opts modsum
------------------------------------------------------------------------------
-- | Find the last typechecked module, and find the most specific span, as well
-- as the judgement at the given range.
judgementForHole
:: IdeState
-> NormalizedFilePath
-> Range
-> FeatureSet
-> MaybeT IO (Range, Judgement, Context, DynFlags)
judgementForHole state nfp range features = do
(asts, amapping) <- runStaleIde state nfp GetHieAst
case asts of
HAR _ _ _ _ (HieFromDisk _) -> fail "Need a fresh hie file"
HAR _ hf _ _ HieFresh -> do
(binds, _) <- runStaleIde state nfp GetBindings
(tcmod, _) <- runStaleIde state nfp TypeCheck
(rss, g) <- liftMaybe $ getSpanAndTypeAtHole amapping range hf
resulting_range <- liftMaybe $ toCurrentRange amapping $ realSrcSpanToRange rss
let (jdg, ctx) = mkJudgementAndContext features g binds rss tcmod
dflags <- getIdeDynflags state nfp
pure (resulting_range, jdg, ctx, dflags)
mkJudgementAndContext
:: FeatureSet
-> Type
-> Bindings
-> RealSrcSpan
-> TcModuleResult
-> (Judgement, Context)
mkJudgementAndContext features g binds rss tcmod = do
let tcg = tmrTypechecked tcmod
tcs = tcg_binds tcg
ctx = mkContext features
(mapMaybe (sequenceA . (occName *** coerce))
$ getDefiningBindings binds rss)
tcg
top_provs = getRhsPosVals rss tcs
local_hy = spliceProvenance top_provs
$ hypothesisFromBindings rss binds
cls_hy = contextMethodHypothesis ctx
in ( mkFirstJudgement
(local_hy <> cls_hy)
(isRhsHole rss tcs)
g
, ctx
)
getSpanAndTypeAtHole
:: PositionMapping
-> Range
-> HieASTs b
-> Maybe (Span, b)
getSpanAndTypeAtHole amapping range hf = do
range' <- fromCurrentRange amapping range
join $ listToMaybe $ M.elems $ flip M.mapWithKey (getAsts hf) $ \fs ast ->
case selectSmallestContaining (rangeToRealSrcSpan (FastString.unpackFS fs) range') ast of
Nothing -> Nothing
Just ast' -> do
let info = nodeInfo ast'
ty <- listToMaybe $ nodeType info
guard $ ("HsUnboundVar","HsExpr") `S.member` nodeAnnotations info
pure (nodeSpan ast', ty)
liftMaybe :: Monad m => Maybe a -> MaybeT m a
liftMaybe a = MaybeT $ pure a
spliceProvenance
:: Map OccName Provenance
-> Hypothesis a
-> Hypothesis a
spliceProvenance provs x =
Hypothesis $ flip fmap (unHypothesis x) $ \hi ->
overProvenance (maybe id const $ M.lookup (hi_name hi) provs) hi
------------------------------------------------------------------------------
-- | Compute top-level position vals of a function
getRhsPosVals :: RealSrcSpan -> TypecheckedSource -> Map OccName Provenance
getRhsPosVals rss tcs
= M.fromList
$ join
$ maybeToList
$ getFirst
$ everything (<>) (mkQ mempty $ \case
TopLevelRHS name ps
(L (RealSrcSpan span) -- body with no guards and a single defn
(HsVar _ (L _ hole)))
| containsSpan rss span -- which contains our span
, isHole $ occName hole -- and the span is a hole
-> First $ do
patnames <- traverse getPatName ps
pure $ zip patnames $ [0..] <&> TopLevelArgPrv name
_ -> mempty
) tcs
------------------------------------------------------------------------------
-- | Is this hole immediately to the right of an equals sign?
isRhsHole :: RealSrcSpan -> TypecheckedSource -> Bool
isRhsHole rss tcs = everything (||) (mkQ False $ \case
TopLevelRHS _ _ (L (RealSrcSpan span) _) -> containsSpan rss span
_ -> False
) tcs