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

Tactic metaprogramming #1776

Merged
merged 57 commits into from
May 16, 2021
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
2cc54a4
Metaprogramming parser
isovector Apr 26, 2021
352dbb0
Enable quasiquotes by default in ghcide
isovector Apr 26, 2021
a32d563
Install the Wingman tactic syntax in ghcide
isovector Apr 26, 2021
643fb88
Underscores are allowed in identifiers too
isovector Apr 26, 2021
22a509f
Test showing we can expand out tactics
isovector Apr 26, 2021
762f43c
Use a more concrete type for mkWingmanMetaprogram
isovector Apr 26, 2021
7ddb88d
Cleanup lexer
isovector Apr 26, 2021
0cd6a0e
Use operator parsing for multiple tactics
isovector Apr 26, 2021
10105a9
Tidy the parser
isovector Apr 26, 2021
65ff472
Add 'obvious' tactic
isovector Apr 26, 2021
f24c7b7
Add intros', which lets you bind the names you want
isovector Apr 26, 2021
6162a81
Add intro to the parser
isovector Apr 26, 2021
dd548ba
Add a modifyDynFlags field to plugins; push it into the parsed module
isovector Apr 26, 2021
fe4f84a
Modify dflags when typechecking too
isovector Apr 26, 2021
7961245
Use the dynflags modifier to remove wingman specific code from ghcide
isovector Apr 27, 2021
3383f64
Move the staticplugin out of ghcide
isovector Apr 27, 2021
b586b85
Add application, add split, rename split to ctor
isovector Apr 27, 2021
74d4e64
Tidy parser
isovector Apr 27, 2021
f488be8
Track remaining subgoals for interactive proofstate
isovector Apr 27, 2021
735a28d
Add a pretty printer of the proofstate
isovector Apr 27, 2021
d18cbf5
Add sorry
isovector Apr 27, 2021
da1bec0
Attach proof state as a hover action
isovector Apr 27, 2021
ce85e9f
Much prettier proof state
isovector Apr 28, 2021
f8abb95
Tidy and tag hover as markdown
isovector Apr 28, 2021
3e3bff5
Be smarter about where we run the dflags endo
isovector Apr 28, 2021
5a57078
Undo changes
isovector Apr 28, 2021
9e74afc
Make Plugin accumulators robust to changing defs
isovector Apr 28, 2021
5adc279
Remove unused exts from Session
isovector Apr 28, 2021
92e95c5
Reannotate the AST with tactic blocks
isovector Apr 28, 2021
2c98fec
Tidy parser
isovector Apr 28, 2021
e3d2e8d
MetaprogramSyntax and exact combinator
isovector Apr 28, 2021
cf6fc2e
Add introduce metaprogram code action
isovector Apr 28, 2021
3c015e4
Merge branch 'master' into metaprogramming2
isovector May 4, 2021
89b7e63
Use noExtField
isovector May 4, 2021
c032f7b
Appease Hlint
isovector May 4, 2021
69d3296
Compat for mkFunTys
isovector May 4, 2021
5657286
Guard the static plugin
isovector May 6, 2021
86e58b9
Static plugin doesn't even need to be here!
isovector May 6, 2021
994662d
textSpaces doesn't exist in old versions of prettyprinter
isovector May 6, 2021
8ad359e
Use a record for the result of judgmentForHole
isovector May 6, 2021
9f43fca
Return the hole's occName
isovector May 6, 2021
0fae78c
Add HoleSort; filter code actions by their holesort
isovector May 6, 2021
4100b2d
Minor tidying
isovector May 6, 2021
1b9cafa
Better name for metaprogram holes
isovector May 6, 2021
7966c15
Merge branch 'master' into metaprogramming2
isovector May 6, 2021
e078635
Use a record for DynFlags modifications
isovector May 6, 2021
4cf4a79
More hlint
isovector May 6, 2021
64350dd
Use modified dynflags for parser with comments
isovector May 8, 2021
5ee9c1f
Allow running custom code blocks
isovector May 8, 2021
1dce53e
Don't assume tactic providers use occnames
isovector May 8, 2021
37ac02b
Revert changes to ghcide
isovector May 12, 2021
a8306f3
Merge branch 'master' into metaprogramming2
isovector May 12, 2021
0399ce4
Add tests
isovector May 13, 2021
6424855
Feature gate
isovector May 13, 2021
6da17ee
Restrict metaprograms to >=8.8
isovector May 14, 2021
38780c1
Merge branch 'master' into metaprogramming2
mergify[bot] May 15, 2021
c986c3f
Merge branch 'master' into metaprogramming2
mergify[bot] May 15, 2021
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
5 changes: 4 additions & 1 deletion ghcide/session-loader/Development/IDE/Session.hs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ import HieDb.Create
import HieDb.Types
import HieDb.Utils
import Maybes (MaybeT (runMaybeT))
import GHC.LanguageExtensions (Extension(EmptyCase))
import GHC.LanguageExtensions (Extension(EmptyCase, QuasiQuotes))

-- | Bump this version number when making changes to the format of the data stored in hiedb
hiedbDataVersion :: String
Expand Down Expand Up @@ -774,13 +774,16 @@ setOptions (ComponentOptions theOpts compRoot _) dflags = do
disableOptimisation $
allowEmptyCaseButWithWarning $
setUpTypedHoles $
enableQuasiQuotes $
makeDynFlagsAbsolute compRoot dflags'
-- initPackages parses the -package flags and
-- sets up the visibility for each component.
-- Throws if a -package flag cannot be satisfied.
(final_df, _) <- liftIO $ wrapPackageSetupException $ initPackages dflags''
return (final_df, targets)

enableQuasiQuotes :: DynFlags -> DynFlags
enableQuasiQuotes = flip xopt_set QuasiQuotes

-- | Wingman wants to support destructing of empty cases, but these are a parse
-- error by default. So we want to enable 'EmptyCase', but then that leads to
Expand Down
41 changes: 40 additions & 1 deletion ghcide/src/Development/IDE/Core/Compile.hs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
{-# LANGUAGE CPP #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE PatternSynonyms #-}
#include "ghc-api-version.h"

-- | Based on https://ghc.haskell.org/trac/ghc/wiki/Commentary/Compiler/API.
Expand Down Expand Up @@ -31,6 +32,7 @@ module Development.IDE.Core.Compile
, setupFinderCache
, getDocsBatch
, lookupName
, pattern WingmanMetaprogram
) where

import Development.IDE.Core.Preprocessor
Expand Down Expand Up @@ -122,6 +124,7 @@ import Data.Unique
import GHC.Fingerprint
import qualified Language.LSP.Server as LSP
import qualified Language.LSP.Types as LSP
import Generics.SYB hiding (orElse)

-- | Given a string buffer, return the string (after preprocessing) and the 'ParsedModule'.
parseModule
Expand Down Expand Up @@ -208,6 +211,36 @@ captureSplices dflags k = do
pure $ f aw'


wingmanMetaprogrammingPlugin :: StaticPlugin
wingmanMetaprogrammingPlugin =
StaticPlugin $ PluginWithArgs (defaultPlugin { parsedResultAction = worker }) []
where
worker :: [CommandLineOption] -> ModSummary -> HsParsedModule -> Hsc HsParsedModule
worker _ _ pm = pure $ pm { hpm_module = addWingmanMetaprogrammingSyntax $ hpm_module pm }


pattern WingmanMetaprogram :: FastString -> HsExpr p
pattern WingmanMetaprogram mp
<- HsSCC _ (SourceText "wingman-meta-program") (StringLiteral NoSourceText mp)
(L _ ( HsVar _ _))

mkWingmanMetaprogram :: SrcSpan -> FastString -> HsExpr GhcPs
mkWingmanMetaprogram ss mp =
HsSCC noExt (SourceText "wingman-meta-program") (StringLiteral NoSourceText mp)
$ L ss
$ HsVar noExt
$ L ss
$ mkRdrUnqual
$ mkVarOcc "_"


addWingmanMetaprogrammingSyntax :: Data a => a -> a
addWingmanMetaprogrammingSyntax =
everywhere $ mkT $ \case
L ss (HsSpliceE _ (HsQuasiQuote _ _ (occNameString . rdrNameOcc -> "wingman") _ mp)) ->
L ss $ mkWingmanMetaprogram ss mp
(x :: LHsExpr GhcPs) -> x

tcRnModule :: HscEnv -> [Linkable] -> ParsedModule -> IO TcModuleResult
tcRnModule hsc_env keep_lbls pmod = do
let ms = pm_mod_summary pmod
Expand Down Expand Up @@ -817,7 +850,13 @@ parseFileContents
-> ExceptT [FileDiagnostic] IO ([FileDiagnostic], ParsedModule)
parseFileContents env customPreprocessor filename ms = do
let loc = mkRealSrcLoc (mkFastString filename) 1 1
dflags = ms_hspp_opts ms
dflags' = ms_hspp_opts ms
dflags =
dflags'
{ staticPlugins =
staticPlugins dflags' <> [wingmanMetaprogrammingPlugin]
isovector marked this conversation as resolved.
Show resolved Hide resolved
}

contents = fromJust $ ms_hspp_buf ms
case unP Parser.parseModule (mkPState dflags contents loc) of
#if MIN_GHC_API_VERSION(8,10,0)
Expand Down
4 changes: 4 additions & 0 deletions plugins/hls-tactics-plugin/hls-tactics-plugin.cabal
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ library
Wingman.LanguageServer
Wingman.LanguageServer.TacticProviders
Wingman.Machinery
Wingman.Metaprogramming.Lexer
Wingman.Metaprogramming.Parser
Wingman.Naming
Wingman.Plugin
Wingman.Range
Expand Down Expand Up @@ -86,6 +88,8 @@ library
, transformers
, unordered-containers
, hyphenation
, megaparsec ^>=9
, parser-combinators

default-language: Haskell2010
default-extensions:
Expand Down
15 changes: 14 additions & 1 deletion plugins/hls-tactics-plugin/src/Wingman/LanguageServer.hs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ import Development.IDE.Graph (Action, RuleResult, Rules, action)
import Development.IDE.Graph.Classes (Typeable, Binary, Hashable, NFData)
import qualified FastString
import GHC.Generics (Generic)
import GhcPlugins (tupleDataCon, consDataCon, substTyAddInScope, ExternalPackageState, HscEnv (hsc_EPS), liftIO)
import GhcPlugins (tupleDataCon, consDataCon, substTyAddInScope, ExternalPackageState, HscEnv (hsc_EPS), liftIO, unpackFS)
import qualified Ide.Plugin.Config as Plugin
import Ide.Plugin.Properties
import Ide.PluginUtils (usePropertyLsp)
Expand All @@ -62,6 +62,10 @@ import Wingman.Judgements.SYB (everythingContaining)
import Wingman.Judgements.Theta
import Wingman.Range
import Wingman.Types
import Generics.SYB hiding (Generic)
import Development.IDE.Core.Compile (pattern WingmanMetaprogram)
import Data.Foldable (for_)
import Wingman.Metaprogramming.Parser (attempt_it)


tacticDesc :: T.Text -> T.Text
Expand Down Expand Up @@ -178,6 +182,10 @@ getIdeDynflags state nfp = do
msr <- unsafeRunStaleIde "getIdeDynflags" state nfp GetModSummaryWithoutTimestamps
pure $ ms_hspp_opts $ msrModSummary msr

getAllMetaprograms :: Data a => a -> [String]
getAllMetaprograms = everything (<>) $ mkQ mempty $ \case
WingmanMetaprogram fs -> [ unpackFS fs ]
(_ :: HsExpr GhcTc) -> mempty

------------------------------------------------------------------------------
-- | Find the last typechecked module, and find the most specific span, as well
Expand All @@ -199,6 +207,7 @@ judgementForHole state nfp range cfg = do
binds <- stale GetBindings
tcg <- fmap (fmap tmrTypechecked)
$ stale TypeCheck

hscenv <- stale GhcSessionDeps

(rss, g) <- liftMaybe $ getSpanAndTypeAtHole range' hf
Expand All @@ -212,6 +221,10 @@ judgementForHole state nfp range cfg = do

(jdg, ctx) <- liftMaybe $ mkJudgementAndContext cfg g binds new_rss tcg eps kt

let mps = getAllMetaprograms $ tcg_binds $ untrackedStaleValue tcg
for_ mps $ \prog ->
liftIO $ putStrLn $ either id show $ attempt_it ctx jdg prog

dflags <- getIdeDynflags state nfp
pure (fmap realSrcSpanToRange new_rss, jdg, ctx, dflags)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ module Wingman.LanguageServer.TacticProviders
, tcCommandId
, TacticParams (..)
, TacticProviderData (..)
, useNameFromHypothesis
) where

import Control.Monad
Expand Down
67 changes: 67 additions & 0 deletions plugins/hls-tactics-plugin/src/Wingman/Metaprogramming/Lexer.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

module Wingman.Metaprogramming.Lexer where

import Control.Applicative
import Control.Monad
import Data.Text (Text)
import qualified Data.Text as T
import Data.Void
import Name
import qualified Text.Megaparsec as P
import qualified Text.Megaparsec.Char as P
import qualified Text.Megaparsec.Char.Lexer as L


type Parser = P.Parsec Void Text

lineComment :: Parser ()
lineComment = L.skipLineComment "--"

blockComment :: Parser ()
blockComment = L.skipBlockComment "{-" "-}"

sc :: Parser ()
sc = L.space P.space1 lineComment blockComment

ichar :: Parser Char
ichar = P.alphaNumChar <|> P.char '_' <|> P.char '\''

lexeme :: Parser a -> Parser a
lexeme = L.lexeme sc

symbol :: Text -> Parser Text
symbol = L.symbol sc

symbol_ :: Text -> Parser ()
symbol_ = void . symbol

brackets :: Parser a -> Parser a
brackets = P.between (symbol "[") (symbol "]")

braces :: Parser a -> Parser a
braces = P.between (symbol "{") (symbol "}")

identifier :: Text -> Parser ()
identifier i = lexeme (P.string i *> P.notFollowedBy ichar)

-- FIXME [Reed M. 2020-10-18] Check to see if the variables are in the reserved list
variable :: Parser OccName
variable = lexeme $ do
c <- P.alphaNumChar
cs <- P.many ichar
pure $ mkVarOcc (c:cs)

-- FIXME [Reed M. 2020-10-18] Check to see if the variables are in the reserved list
name :: Parser Text
name = lexeme $ do
c <- P.alphaNumChar
cs <- P.many (ichar <|> P.char '-')
pure $ T.pack (c:cs)

keyword :: Text -> Parser ()
keyword = identifier

83 changes: 83 additions & 0 deletions plugins/hls-tactics-plugin/src/Wingman/Metaprogramming/Parser.hs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TypeApplications #-}

module Wingman.Metaprogramming.Parser where

import qualified Control.Monad.Combinators.Expr as P
import Data.Functor
import Development.IDE.GHC.Compat (alphaTyVars, LHsExpr, GhcPs)
import GhcPlugins (mkTyVarTy)
import qualified Refinery.Tactic as R
import qualified Text.Megaparsec as P
import Wingman.Auto
import Wingman.LanguageServer.TacticProviders (useNameFromHypothesis)
import Wingman.Metaprogramming.Lexer
import Wingman.Tactics
import Wingman.Types
import qualified Data.Text as T


nullary :: T.Text -> TacticsM () -> Parser (TacticsM ())
nullary name tac = identifier name $> tac

unary_occ :: T.Text -> (OccName -> TacticsM ()) -> Parser (TacticsM ())
unary_occ name tac = tac <$> (identifier name *> variable)

variadic_occ :: T.Text -> ([OccName] -> TacticsM ()) -> Parser (TacticsM ())
variadic_occ name tac = tac <$> (identifier name *> P.many variable)


tactic :: Parser (TacticsM ())
tactic = flip P.makeExprParser operators $ P.choice
[ braces tactic
, nullary "assumption" assumption
, unary_occ "assume" assume
, variadic_occ "intros" $ \case
[] -> intros
names -> intros' $ Just names
, nullary "destruct_all" destructAll
, unary_occ "destruct" $ useNameFromHypothesis destruct
, unary_occ "homo" $ useNameFromHypothesis homo
, unary_occ "apply" $ useNameFromHypothesis apply
, unary_occ "split" userSplit
, nullary "obvious" obvious
, nullary "auto" auto
, R.try <$> (keyword "try" *> tactic)
]

bindOne :: TacticsM a -> TacticsM a -> TacticsM a
bindOne t t1 = t R.<@> [t1]

operators :: [[P.Operator Parser (TacticsM ())]]
operators =
[ [ P.Prefix (symbol "*" $> R.many_) ]
, [ P.InfixR (symbol "|" $> (R.<%>) )]
, [ P.InfixL (symbol ";" $> (>>))
, P.InfixL (symbol "," $> bindOne)
]
]


skolems :: [Type]
skolems = fmap mkTyVarTy alphaTyVars

a_skolem, b_skolem, c_skolem :: Type
(a_skolem : b_skolem : c_skolem : _) = skolems


attempt_it :: Context -> Judgement -> String -> Either String (LHsExpr GhcPs)
attempt_it ctx jdg program =
case P.runParser (sc *> tactic <* P.eof) "<splice>" (T.pack program) of
Left peb -> Left $ P.errorBundlePretty peb
Right tt -> do
case runTactic
ctx
jdg
tt
of
Left tes -> Left $ show tes
Right rtr -> Right $ rtr_extract rtr

25 changes: 20 additions & 5 deletions plugins/hls-tactics-plugin/src/Wingman/Tactics.hs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import Control.Monad (unless)
import Control.Monad.Except (throwError)
import Control.Monad.Reader.Class (MonadReader (ask))
import Control.Monad.State.Strict (StateT(..), runStateT)
import Data.Bool (bool)
import Data.Foldable
import Data.Functor ((<&>))
import Data.Generics.Labels ()
Expand Down Expand Up @@ -93,17 +94,26 @@ restrictPositionForApplication f app = do
------------------------------------------------------------------------------
-- | Introduce a lambda binding every variable.
intros :: TacticsM ()
intros = rule $ \jdg -> do
intros = intros' Nothing

------------------------------------------------------------------------------
-- | Introduce a lambda binding every variable.
intros'
:: Maybe [OccName] -- ^ When 'Nothing', generate a new name for every
-- variable. Otherwise, only bind the variables named.
-> TacticsM ()
intros' names = rule $ \jdg -> do
let g = jGoal jdg
ctx <- ask
case tcSplitFunTys $ unCType g of
([], _) -> throwError $ GoalMismatch "intros" g
(as, b) -> do
let vs = mkManyGoodNames (hyNamesInScope $ jEntireHypothesis jdg) as
let top_hole = isTopHole ctx jdg
let vs = fromMaybe (mkManyGoodNames (hyNamesInScope $ jEntireHypothesis jdg) as) names
num_args = length vs
top_hole = isTopHole ctx jdg
hy' = lambdaHypothesis top_hole $ zip vs $ coerce as
jdg' = introduce hy'
$ withNewGoal (CType b) jdg
$ withNewGoal (CType $ mkFunTys (drop num_args as) b) jdg
ext <- newSubgoal jdg'
pure $
ext
Expand Down Expand Up @@ -255,6 +265,12 @@ splitSingle = tracing "splitSingle" $ do
splitDataCon dc
_ -> throwError $ GoalMismatch "splitSingle" g

------------------------------------------------------------------------------
-- | Like 'split', but prunes any data constructors which have holes.
obvious :: TacticsM ()
obvious = tracing "obvious" $ do
pruning split $ bool (Just NoProgress) Nothing . null


------------------------------------------------------------------------------
-- | Allow the given tactic to proceed if and only if it introduces holes that
Expand Down Expand Up @@ -402,4 +418,3 @@ applyByName name = do
True -> apply hi
False -> empty