Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Metaprogramming interface to tactics #524

Closed
wants to merge 66 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
3527cd2
WIP recursion
isovector Oct 3, 2020
da2d729
Better scoring heuristic
isovector Oct 4, 2020
b805aa9
Don't trace scoring
isovector Oct 4, 2020
1dbec5b
Add fromMaybe test
isovector Oct 4, 2020
803c8d7
wtf; recursion stack frame doesnt seem to work
isovector Oct 4, 2020
fe468f3
Better debugging and smarter apply'
isovector Oct 5, 2020
68cda51
filterT
isovector Oct 5, 2020
a480c19
Fix FilterT
TOTBWF Oct 5, 2020
8fa2a83
derive foldr
isovector Oct 5, 2020
6dee3a2
Merge branch 'recursion-frame-wtf' of github.com:isovector/haskell-la…
isovector Oct 5, 2020
a6e40e1
Test for foldr
isovector Oct 5, 2020
7981a71
Fix JoinCont test
isovector Oct 5, 2020
69d122b
Use generic-lens
isovector Oct 5, 2020
a781803
Positionally-aware recursion
isovector Oct 6, 2020
60ebb74
almost generate fmap for binary tree
isovector Oct 6, 2020
9714f7e
timeout tactic if it's too slow
isovector Oct 6, 2020
3ac43d8
Simpler recursive hypothesis
isovector Oct 6, 2020
89a488a
Remove current position from jdg
isovector Oct 6, 2020
c774fe1
Restrict homomorphic splits to positional args
isovector Oct 6, 2020
4b682a2
Silence a trace
isovector Oct 6, 2020
ab2e592
Better data provenance
isovector Oct 7, 2020
5ddd360
Support multiple positional binding sets
isovector Oct 7, 2020
a3dc935
Prune destructing on pattern vals if they don't introduce new types
isovector Oct 7, 2020
abd365f
Fix pruning to actually run the tactic
isovector Oct 8, 2020
8d53032
Count duplicates
isovector Oct 8, 2020
b8b0d0d
?
isovector Oct 8, 2020
ec13e9f
Don't allow splitting if it doesn't buy you anything
isovector Oct 8, 2020
683c791
Known fmap strategy
isovector Oct 8, 2020
4114d03
Commit to a known solution if one exists
isovector Oct 9, 2020
6ff6504
Compute top level position vals
isovector Oct 9, 2020
29858b0
Add tophole to jdg
isovector Oct 9, 2020
929bcc9
Cleanup ugly hack in intros! \o/
isovector Oct 9, 2020
3272a1e
Fix improperly pruning split of split
isovector Oct 9, 2020
30c9b58
Tracing
isovector Oct 11, 2020
6b8a0b9
unpack introduced bindings
isovector Oct 11, 2020
872cfec
Slightly better format
isovector Oct 11, 2020
8a416d9
compress ppr tree
isovector Oct 11, 2020
621a04d
Merge pull request #26 from isovector/tracing
isovector Oct 13, 2020
8e69004
Fix splitAuto
isovector Oct 13, 2020
a84195c
Cleanup traces
isovector Oct 13, 2020
5f44746
Fix autosplit
isovector Oct 13, 2020
04774c1
Merge branch 'master' into recursion
isovector Oct 16, 2020
4eca10d
Update refinery
isovector Oct 16, 2020
de0cfb9
Attempt GHC compatability
isovector Oct 16, 2020
1b70cee
cpp nightmares
isovector Oct 16, 2020
b700c35
type syn of type syn
isovector Oct 16, 2020
25b9133
wtfff
isovector Oct 16, 2020
baac450
maybe this time
isovector Oct 16, 2020
c6e10fd
omg plzz 810
isovector Oct 16, 2020
c26f9ba
dsgjdsgidgjis
isovector Oct 16, 2020
0ac041b
Get known metaprogramming files
isovector Oct 18, 2020
2ce3bf1
metaprogram and cache
isovector Oct 18, 2020
5a04900
Shake rules for the metaprogram cache
isovector Oct 18, 2020
0861f11
Hook up metaprograms
isovector Oct 18, 2020
d6f4617
Implement tactic parser
TOTBWF Oct 18, 2020
ec1ca17
fix loading metaprograms
isovector Oct 18, 2020
c5ff512
Parse annotations
isovector Oct 18, 2020
232a0db
Connect @auto metaprograms to the auto tactic
isovector Oct 19, 2020
ea77d9c
Auto can use known strategies too
isovector Oct 19, 2020
5de6c01
Merge branch 'master' of github.com:isovector/haskell-language-server…
isovector Oct 21, 2020
333dc72
Merge branch 'master' into metaprogramming
isovector Oct 21, 2020
d33af43
Upgrade megaparsec to 9.0.0
isovector Oct 21, 2020
63d115c
Properly split fun type
isovector Oct 21, 2020
3f9668d
CPP for mkVisFunTys in older versions of GHC
isovector Oct 21, 2020
9616404
Wrong comparison
isovector Oct 21, 2020
e79f238
Empty context for quickcheck tests
isovector Oct 21, 2020
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
8 changes: 8 additions & 0 deletions plugins/tactics/hls-tactics-plugin.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,9 @@ library
Ide.Plugin.Tactic.Judgements
Ide.Plugin.Tactic.KnownStrategies
Ide.Plugin.Tactic.Machinery
Ide.Plugin.Tactic.Metaprogramming
Ide.Plugin.Tactic.Naming
Ide.Plugin.Tactic.Parser
Ide.Plugin.Tactic.Range
Ide.Plugin.Tactic.Tactics
Ide.Plugin.Tactic.Types
Expand All @@ -54,7 +56,10 @@ library
build-depends:
, aeson
, base >=4.12 && <5
, binary
, bytestring
, containers
, deepseq
, directory
, extra
, filepath
Expand All @@ -65,10 +70,13 @@ library
, ghc-exactprint
, ghc-source-gen
, ghcide >=0.1
, hashable
, haskell-lsp ^>=0.22
, hls-plugin-api
, lens
, megaparsec ^>=9
, mtl
, parser-combinators
, refinery ^>=0.3
, retrie >=0.1.1.0
, shake >=0.17.5
Expand Down
126 changes: 103 additions & 23 deletions plugins/tactics/src/Ide/Plugin/Tactic.hs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
{-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DerivingStrategies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NumDecimals #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ViewPatterns #-}

-- | A plugin that uses tactics to synthesize code
Expand All @@ -17,28 +19,35 @@ module Ide.Plugin.Tactic
) where

import Control.Arrow
import Control.DeepSeq (NFData)
import Control.Monad
import Control.Monad.Error.Class
import Control.Monad.Trans
import Control.Monad.Trans.Maybe
import Data.Aeson
import Data.Binary (Binary)
import qualified Data.ByteString.Char8 as BS
import Data.Coerce
import Data.Generics.Aliases (mkQ)
import Data.Generics.Schemes (everything)
import Data.Hashable (Hashable)
import Data.List
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 qualified Data.Text.IO as T
import Data.Traversable
import Development.IDE (ShowDiagnostic(ShowDiag))
import Development.IDE.Core.PositionMapping
import Development.IDE.Core.RuleTypes
import Development.IDE.Core.Service (runAction)
import Development.IDE.Core.Shake (useWithStale, IdeState (..))
import Development.IDE.Core.Shake (GetModificationTime (..), defineEarlyCutoff, define, use, useWithStale, IdeState (..))
import Development.IDE.GHC.Compat
import Development.IDE.GHC.Error (realSrcSpanToRange)
import Development.IDE.Spans.LocalBindings (getDefiningBindings)
import Development.Shake (Action)
import Development.Shake (RuleResult, Rules, Action)
import DynFlags (xopt)
import qualified FastString
import GHC.Generics (Generic)
Expand All @@ -48,6 +57,7 @@ import Ide.Plugin.Tactic.Auto
import Ide.Plugin.Tactic.Context
import Ide.Plugin.Tactic.GHC
import Ide.Plugin.Tactic.Judgements
import Ide.Plugin.Tactic.Metaprogramming
import Ide.Plugin.Tactic.Range
import Ide.Plugin.Tactic.Tactics
import Ide.Plugin.Tactic.TestTypes
Expand All @@ -71,15 +81,66 @@ descriptor plId = (defaultPluginDescriptor plId)
(tacticCmd $ commandTactic tc))
[minBound .. maxBound]
, pluginCodeActionProvider = Just codeActionProvider
, pluginRules = tacticRules
}

data GetMetaprogram = GetMetaprogram
deriving stock (Show, Generic, Eq, Ord)
deriving anyclass (Hashable, NFData, Binary)

type instance RuleResult GetMetaprogram = Metaprogram

data GetMetaprogramCache = GetMetaprogramCache
deriving stock (Show, Generic, Eq, Ord)
deriving anyclass (Hashable, NFData, Binary)

type instance RuleResult GetMetaprogramCache = MetaprogramCache

tacticRules :: Rules ()
tacticRules = do
defineEarlyCutoff $ \GetMetaprogram nfp -> do
mtime <- use GetModificationTime nfp
contents <- liftIO $ T.readFile $ fromNormalizedFilePath nfp
pure
$ (Just $ BS.pack $ show mtime ,)
$ case parseMetaprogram (fromNormalizedFilePath nfp) contents of
Left err ->
( [ (nfp
, ShowDiag
, Diagnostic
(Range (Position 0 0)
(Position 0 0))
Nothing
Nothing
Nothing
(T.pack err)
Nothing
Nothing
)
]
, Nothing
)
Right mp -> ([], Just mp)
define $ \GetMetaprogramCache _ -> do
files <- liftIO getKnownFiles
Copy link
Collaborator

Choose a reason for hiding this comment

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

GetMetaprogramCache won't be recomputed when the result of getKnownFiles changes.

Also, there's already a function called getKnownFiles in ghcide, you should rename it to avoid confusion.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch. What's the right way to discover new files in shake?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Create an early cutoff rule for getKnownFiles, and use alwaysRerun.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Although that might not be such a good idea, since it would call ls every time a codeaction is generated, right?

Copy link
Collaborator

Choose a reason for hiding this comment

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

I guess you need to register a file watcher with the client, like we were discussing earlier on IRC.

mps <- fmap catMaybes $ traverse (use GetMetaprogram) files
pure ([], Just $ buildCache mps)


tacticDesc :: T.Text -> T.Text
tacticDesc name = "fill the hole using the " <> name <> " tactic"

------------------------------------------------------------------------------
-- | A 'TacticProvider' is a way of giving context-sensitive actions to the LS
-- UI.
type TacticProvider = DynFlags -> PluginId -> Uri -> Range -> Judgement -> IO [CAResult]
type TacticProvider
= DynFlags
-> MetaprogramCache
-> PluginId
-> Uri
-> Range
-> Judgement
-> IO [CAResult]


------------------------------------------------------------------------------
Expand Down Expand Up @@ -115,17 +176,23 @@ commandProvider HomomorphismLambdaCase =
requireExtension LambdaCase $
filterGoalType ((== Just True) . lambdaCaseable) $
provide HomomorphismLambdaCase ""
commandProvider RunMetaprogram =
allMetaprograms $ \mp ->
provide RunMetaprogram mp


------------------------------------------------------------------------------
-- | A mapping from tactic commands to actual tactics for refinery.
commandTactic :: TacticCommand -> OccName -> TacticsM ()
commandTactic Auto = const auto
commandTactic Intros = const intros
commandTactic Destruct = destruct
commandTactic Homomorphism = homo
commandTactic DestructLambdaCase = const destructLambdaCase
commandTactic HomomorphismLambdaCase = const homoLambdaCase
commandTactic :: TacticCommand -> Maybe Metaprogram -> OccName -> TacticsM ()
commandTactic Auto _ _ = auto
commandTactic Intros _ _ = intros
commandTactic Destruct _ occ = destruct occ
commandTactic Homomorphism _ occ = homo occ
commandTactic DestructLambdaCase _ _ = destructLambdaCase
commandTactic HomomorphismLambdaCase _ _ = homoLambdaCase
commandTactic RunMetaprogram (Just mp) _ = mp_program mp
-- TODO(sandy): better error here
commandTactic RunMetaprogram Nothing _ = throwError NoApplicableTactic


------------------------------------------------------------------------------
Expand All @@ -152,11 +219,12 @@ codeActionProvider :: CodeActionProvider
codeActionProvider _conf state plId (TextDocumentIdentifier uri) range _ctx
| Just nfp <- uriToNormalizedFilePath $ toNormalizedUri uri =
fromMaybeT (Right $ List []) $ do
(_, jdg, _, dflags) <- judgementForHole state nfp range
(_, jdg, _, dflags, mpc) <- judgementForHole state nfp range
actions <- lift $
-- This foldMap is over the function monoid.
foldMap commandProvider [minBound .. maxBound]
dflags
mpc
plId
uri
range
Expand All @@ -173,7 +241,7 @@ codeActions = List . fmap CACodeAction
-- | Terminal constructor for providing context-sensitive tactics. Tactics
-- given by 'provide' are always available.
provide :: TacticCommand -> T.Text -> TacticProvider
provide tc name _ plId uri range _ = do
provide tc name _ _ plId uri range _ = do
let title = tacticTitle tc name
params = TacticParams { file = uri , range = range , var_name = name }
cmd <- mkLspCommand plId (tcCommandId tc) title (Just [toJSON params])
Expand All @@ -188,19 +256,19 @@ provide tc name _ plId uri range _ = do
-- | Restrict a 'TacticProvider', making sure it appears only when the given
-- predicate holds for the goal.
requireExtension :: Extension -> TacticProvider -> TacticProvider
requireExtension ext tp dflags plId uri range jdg =
requireExtension ext tp dflags mpc plId uri range jdg =
case xopt ext dflags of
True -> tp dflags plId uri range jdg
True -> tp dflags mpc plId uri range jdg
False -> pure []


------------------------------------------------------------------------------
-- | Restrict a 'TacticProvider', making sure it appears only when the given
-- predicate holds for the goal.
filterGoalType :: (Type -> Bool) -> TacticProvider -> TacticProvider
filterGoalType p tp dflags plId uri range jdg =
filterGoalType p tp dflags mpc plId uri range jdg =
case p $ unCType $ jGoal jdg of
True -> tp dflags plId uri range jdg
True -> tp dflags mpc plId uri range jdg
False -> pure []


Expand All @@ -211,14 +279,20 @@ filterBindingType
:: (Type -> Type -> Bool) -- ^ Goal and then binding types.
-> (OccName -> Type -> TacticProvider)
-> TacticProvider
filterBindingType p tp dflags plId uri range jdg =
filterBindingType p tp dflags mpc plId uri range jdg =
let hy = jHypothesis jdg
g = jGoal jdg
in fmap join $ for (M.toList hy) $ \(occ, CType ty) ->
case p (unCType g) ty of
True -> tp occ ty dflags plId uri range jdg
True -> tp occ ty dflags mpc plId uri range jdg
False -> pure []

allMetaprograms
:: (T.Text -> TacticProvider)
-> TacticProvider
allMetaprograms f dflags mpc@(MetaprogramCache cache) plId uri range jdg =
fmap join $ for (M.keys cache) $ \key -> f key dflags mpc plId uri range jdg


data TacticParams = TacticParams
{ file :: Uri -- ^ Uri of the file to fill the hole in
Expand All @@ -235,7 +309,7 @@ judgementForHole
:: IdeState
-> NormalizedFilePath
-> Range
-> MaybeT IO (Range, Judgement, Context, DynFlags)
-> MaybeT IO (Range, Judgement, Context, DynFlags, MetaprogramCache)
judgementForHole state nfp range = do
(asts, amapping) <- MaybeT $ runIde state $ useWithStale GetHieAst nfp
range' <- liftMaybe $ fromCurrentRange amapping range
Expand All @@ -258,9 +332,11 @@ judgementForHole state nfp range = do

resulting_range <- liftMaybe $ toCurrentRange amapping $ realSrcSpanToRange rss
(tcmod, _) <- MaybeT $ runIde state $ useWithStale TypeCheck nfp
mpc <- MaybeT $ runIde state $ use GetMetaprogramCache nfp
let tcg = fst $ tm_internals_ $ tmrModule tcmod
tcs = tm_typechecked_source $ tmrModule tcmod
ctx = mkContext
mpc
(mapMaybe (sequenceA . (occName *** coerce))
$ getDefiningBindings binds rss)
tcg
Expand All @@ -276,20 +352,24 @@ judgementForHole state nfp range = do
goal
, ctx
, dflags
, mpc
)



tacticCmd :: (OccName -> TacticsM ()) -> CommandFunction TacticParams
tacticCmd :: (Maybe Metaprogram -> OccName -> TacticsM ()) -> CommandFunction TacticParams
-- TODO(sandy): This is gross; it is reusing var_name as a metaprogram name as well
tacticCmd tac lf state (TacticParams uri range var_name)
| Just nfp <- uriToNormalizedFilePath $ toNormalizedUri uri =
fromMaybeT (Right Null, Nothing) $ do
(range', jdg, ctx, dflags) <- judgementForHole state nfp range
(range', jdg, ctx, dflags, mpc) <- judgementForHole state nfp range
let span = rangeToRealSrcSpan (fromNormalizedFilePath nfp) range'
pm <- MaybeT $ useAnnotatedSource "tacticsCmd" state nfp
pm <- MaybeT $ useAnnotatedSource "tacticsCmd" state nfp
let mp = M.lookup var_name $ unMetaprogramCache mpc

x <- lift $ timeout 2e8 $
case runTactic ctx jdg
$ tac
$ tac mp
$ mkVarOcc
$ T.unpack var_name of
Left err ->
Expand Down
2 changes: 1 addition & 1 deletion plugins/tactics/src/Ide/Plugin/Tactic/CodeGen.hs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE ViewPatterns #-}
Expand Down Expand Up @@ -191,7 +192,6 @@ coerceName :: HasOccName a => a -> RdrNameStr
coerceName = fromString . occNameString . occName



------------------------------------------------------------------------------
-- | Like 'var', but works over standard GHC 'OccName's.
var' :: Var a => OccName -> a
Expand Down
26 changes: 17 additions & 9 deletions plugins/tactics/src/Ide/Plugin/Tactic/Context.hs
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ import OccName
import TcRnTypes


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


splitId :: Id -> (OccName, CType)
Expand All @@ -35,8 +38,13 @@ getFunBindId _ = []


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


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


getMetaprogramCache :: MonadReader Context m => m MetaprogramCache
getMetaprogramCache = asks ctxMetaprogramCache

17 changes: 15 additions & 2 deletions plugins/tactics/src/Ide/Plugin/Tactic/GHC.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,21 @@ cloneTyVar t =
------------------------------------------------------------------------------
-- | Is this a function type?
isFunction :: Type -> Bool
isFunction (tcSplitFunTys -> ((_:_), _)) = True
isFunction _ = False
isFunction (tacticsSplitFunTy -> (_, _, [], _)) = False
isFunction _ = True


tacticsSplitFunTy :: Type -> ([TyVar], ThetaType, [Type], Type)
tacticsSplitFunTy t
= let (vars, theta, t') = tcSplitSigmaTy t
(args, res) = tcSplitFunTys t'
in (vars, theta, args, res)

#if __GLASGOW_HASKELL__ < 810
mkVisFunTys :: [Type] -> Type -> Type
mkVisFunTys = mkFunTys
#endif


------------------------------------------------------------------------------
-- | Is this an algebraic type?
Expand Down
Loading