packages feed

hls-tactics-plugin 0.5.1.0 → 1.0.0.0

raw patch · 24 files changed

+1700/−670 lines, 24 filesdep +bytestringdep +data-defaultdep +hspec-expectationsdep −haskell-lspdep ~basedep ~ghcidedep ~hls-plugin-apinew-component:exe:test-serverPVP ok

version bump matches the API change (PVP)

Dependencies added: bytestring, data-default, hspec-expectations, lsp, lsp-test, lsp-types, megaparsec, tasty, tasty-ant-xml, tasty-expected-failure, tasty-golden, tasty-hunit, tasty-rerun

Dependencies removed: haskell-lsp

Dependency ranges changed: base, ghcide, hls-plugin-api, shake

API changes (from Hackage documentation)

- Ide.Plugin.Tactic: instance Data.Aeson.Types.FromJSON.FromJSON Ide.Plugin.Tactic.TacticParams
- Ide.Plugin.Tactic: instance Data.Aeson.Types.ToJSON.ToJSON Ide.Plugin.Tactic.TacticParams
- Ide.Plugin.Tactic: instance GHC.Classes.Eq Ide.Plugin.Tactic.TacticParams
- Ide.Plugin.Tactic: instance GHC.Generics.Generic Ide.Plugin.Tactic.TacticParams
- Ide.Plugin.Tactic: instance GHC.Show.Show Ide.Plugin.Tactic.TacticParams
- Ide.Plugin.Tactic.CodeGen: appDollar :: HsExpr GhcPs -> HsExpr GhcPs -> HsExpr GhcPs
- Ide.Plugin.Tactic.CodeGen: bvar' :: BVar a => OccName -> a
- Ide.Plugin.Tactic.CodeGen: coerceName :: HasOccName a => a -> RdrNameStr
- Ide.Plugin.Tactic.CodeGen: infixCall :: String -> HsExpr GhcPs -> HsExpr GhcPs -> HsExpr GhcPs
- Ide.Plugin.Tactic.CodeGen: mkCon :: DataCon -> [LHsExpr GhcPs] -> LHsExpr GhcPs
- Ide.Plugin.Tactic.CodeGen: mkFunc :: String -> HsExpr GhcPs
- Ide.Plugin.Tactic.CodeGen: mkVal :: String -> HsExpr GhcPs
- Ide.Plugin.Tactic.CodeGen: var' :: Var a => OccName -> a
- Ide.Plugin.Tactic.GHC: fromPatCompat :: PatCompat GhcTc -> Pat GhcTc
- Ide.TreeTransform: data Graft a
- Ide.TreeTransform: graft :: forall a. Data a => SrcSpan -> LHsExpr GhcPs -> Graft a
- Ide.TreeTransform: instance GHC.Base.Monoid (Ide.TreeTransform.Graft a)
- Ide.TreeTransform: instance GHC.Base.Semigroup (Ide.TreeTransform.Graft a)
- Ide.TreeTransform: transform :: DynFlags -> ClientCapabilities -> Uri -> Graft ParsedSource -> Annotated ParsedSource -> Either String WorkspaceEdit
- Ide.TreeTransform: useAnnotatedSource :: String -> IdeState -> NormalizedFilePath -> IO (Maybe (Annotated ParsedSource))
+ Ide.Plugin.Tactic.CaseSplit: iterateSplit :: AgdaMatch -> [AgdaMatch]
+ Ide.Plugin.Tactic.CaseSplit: mkFirstAgda :: [Pat GhcPs] -> HsExpr GhcPs -> AgdaMatch
+ Ide.Plugin.Tactic.CaseSplit: splitToDecl :: OccName -> [AgdaMatch] -> LHsDecl GhcPs
+ Ide.Plugin.Tactic.CodeGen.Utils: appDollar :: HsExpr GhcPs -> HsExpr GhcPs -> HsExpr GhcPs
+ Ide.Plugin.Tactic.CodeGen.Utils: bvar' :: BVar a => OccName -> a
+ Ide.Plugin.Tactic.CodeGen.Utils: coerceName :: HasOccName a => a -> RdrNameStr
+ Ide.Plugin.Tactic.CodeGen.Utils: infixCall :: String -> HsExpr GhcPs -> HsExpr GhcPs -> HsExpr GhcPs
+ Ide.Plugin.Tactic.CodeGen.Utils: mkCon :: DataCon -> [LHsExpr GhcPs] -> LHsExpr GhcPs
+ Ide.Plugin.Tactic.CodeGen.Utils: mkFunc :: String -> HsExpr GhcPs
+ Ide.Plugin.Tactic.CodeGen.Utils: mkVal :: String -> HsExpr GhcPs
+ Ide.Plugin.Tactic.CodeGen.Utils: var' :: Var a => OccName -> a
+ Ide.Plugin.Tactic.Debug: traceFX :: String -> (a -> String) -> a -> a
+ Ide.Plugin.Tactic.FeatureSet: CantHaveAnEmptyDataType :: Feature
+ Ide.Plugin.Tactic.FeatureSet: allFeatures :: FeatureSet
+ Ide.Plugin.Tactic.FeatureSet: data Feature
+ Ide.Plugin.Tactic.FeatureSet: defaultFeatures :: FeatureSet
+ Ide.Plugin.Tactic.FeatureSet: hasFeature :: Feature -> FeatureSet -> Bool
+ Ide.Plugin.Tactic.FeatureSet: instance GHC.Classes.Eq Ide.Plugin.Tactic.FeatureSet.Feature
+ Ide.Plugin.Tactic.FeatureSet: instance GHC.Classes.Ord Ide.Plugin.Tactic.FeatureSet.Feature
+ Ide.Plugin.Tactic.FeatureSet: instance GHC.Enum.Bounded Ide.Plugin.Tactic.FeatureSet.Feature
+ Ide.Plugin.Tactic.FeatureSet: instance GHC.Enum.Enum Ide.Plugin.Tactic.FeatureSet.Feature
+ Ide.Plugin.Tactic.FeatureSet: instance GHC.Read.Read Ide.Plugin.Tactic.FeatureSet.Feature
+ Ide.Plugin.Tactic.FeatureSet: instance GHC.Show.Show Ide.Plugin.Tactic.FeatureSet.Feature
+ Ide.Plugin.Tactic.FeatureSet: parseFeatureSet :: Text -> FeatureSet
+ Ide.Plugin.Tactic.FeatureSet: prettyFeatureSet :: FeatureSet -> String
+ Ide.Plugin.Tactic.FeatureSet: type FeatureSet = Set Feature
+ Ide.Plugin.Tactic.GHC: allOccNames :: Data a => a -> Set OccName
+ Ide.Plugin.Tactic.GHC: containsHole :: Data a => a -> Bool
+ Ide.Plugin.Tactic.GHC: containsHsVar :: Data a => RdrName -> a -> Bool
+ Ide.Plugin.Tactic.GHC: eqRdrName :: RdrName -> RdrName -> Bool
+ Ide.Plugin.Tactic.GHC: fromPatCompatPs :: PatCompat GhcPs -> Pat GhcPs
+ Ide.Plugin.Tactic.GHC: fromPatCompatTc :: PatCompat GhcTc -> Pat GhcTc
+ Ide.Plugin.Tactic.GHC: getRecordFields :: DataCon -> Maybe [(OccName, CType)]
+ Ide.Plugin.Tactic.GHC: isHole :: OccName -> Bool
+ Ide.Plugin.Tactic.GHC: pattern Case :: HsExpr GhcPs -> [(Pat GhcPs, HsExpr GhcPs)] -> HsExpr GhcPs
+ Ide.Plugin.Tactic.GHC: pattern Lambda :: [Pat GhcPs] -> HsExpr GhcPs -> HsExpr GhcPs
+ Ide.Plugin.Tactic.GHC: pattern SinglePatMatch :: Pat GhcPs -> HsExpr GhcPs -> Match GhcPs (LHsExpr GhcPs)
+ Ide.Plugin.Tactic.GHC: pattern UnguardedRHSs :: HsExpr GhcPs -> GRHSs GhcPs (LHsExpr GhcPs)
+ Ide.Plugin.Tactic.GHC: unXPat :: Pat GhcPs -> Pat GhcPs
+ Ide.Plugin.Tactic.GHC: unpackMatches :: [Match GhcPs (LHsExpr GhcPs)] -> Maybe [(Pat GhcPs, HsExpr GhcPs)]
+ Ide.Plugin.Tactic.Judgements: hyByName :: Hypothesis a -> Map OccName (HyInfo a)
+ Ide.Plugin.Tactic.Judgements: hyNamesInScope :: Hypothesis a -> Set OccName
+ Ide.Plugin.Tactic.LanguageServer: getFeatureSet :: MonadLsp Config m => ShakeExtras -> m FeatureSet
+ Ide.Plugin.Tactic.LanguageServer: getIdeDynflags :: IdeState -> NormalizedFilePath -> MaybeT IO DynFlags
+ Ide.Plugin.Tactic.LanguageServer: getRhsPosVals :: RealSrcSpan -> TypecheckedSource -> Map OccName Provenance
+ Ide.Plugin.Tactic.LanguageServer: getSpanAndTypeAtHole :: PositionMapping -> Range -> HieASTs b -> Maybe (Span, b)
+ Ide.Plugin.Tactic.LanguageServer: isRhsHole :: RealSrcSpan -> TypecheckedSource -> Bool
+ Ide.Plugin.Tactic.LanguageServer: judgementForHole :: IdeState -> NormalizedFilePath -> Range -> FeatureSet -> MaybeT IO (Range, Judgement, Context, DynFlags)
+ Ide.Plugin.Tactic.LanguageServer: liftMaybe :: Monad m => Maybe a -> MaybeT m a
+ Ide.Plugin.Tactic.LanguageServer: mkJudgementAndContext :: FeatureSet -> Type -> Bindings -> RealSrcSpan -> TcModuleResult -> (Judgement, Context)
+ Ide.Plugin.Tactic.LanguageServer: runIde :: IdeState -> Action a -> IO a
+ Ide.Plugin.Tactic.LanguageServer: 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)
+ Ide.Plugin.Tactic.LanguageServer: spliceProvenance :: Map OccName Provenance -> Hypothesis a -> Hypothesis a
+ Ide.Plugin.Tactic.LanguageServer: tacticDesc :: Text -> Text
+ Ide.Plugin.Tactic.LanguageServer: tcCommandName :: TacticCommand -> Text
+ Ide.Plugin.Tactic.LanguageServer.TacticProviders: TacticParams :: Uri -> Range -> Text -> TacticParams
+ Ide.Plugin.Tactic.LanguageServer.TacticProviders: [tp_file] :: TacticParams -> Uri
+ Ide.Plugin.Tactic.LanguageServer.TacticProviders: [tp_range] :: TacticParams -> Range
+ Ide.Plugin.Tactic.LanguageServer.TacticProviders: [tp_var_name] :: TacticParams -> Text
+ Ide.Plugin.Tactic.LanguageServer.TacticProviders: commandProvider :: TacticCommand -> TacticProvider
+ Ide.Plugin.Tactic.LanguageServer.TacticProviders: commandTactic :: TacticCommand -> OccName -> TacticsM ()
+ Ide.Plugin.Tactic.LanguageServer.TacticProviders: data TacticParams
+ Ide.Plugin.Tactic.LanguageServer.TacticProviders: instance Data.Aeson.Types.FromJSON.FromJSON Ide.Plugin.Tactic.LanguageServer.TacticProviders.TacticParams
+ Ide.Plugin.Tactic.LanguageServer.TacticProviders: instance Data.Aeson.Types.ToJSON.ToJSON Ide.Plugin.Tactic.LanguageServer.TacticProviders.TacticParams
+ Ide.Plugin.Tactic.LanguageServer.TacticProviders: instance GHC.Classes.Eq Ide.Plugin.Tactic.LanguageServer.TacticProviders.TacticParams
+ Ide.Plugin.Tactic.LanguageServer.TacticProviders: instance GHC.Generics.Generic Ide.Plugin.Tactic.LanguageServer.TacticProviders.TacticParams
+ Ide.Plugin.Tactic.LanguageServer.TacticProviders: instance GHC.Show.Show Ide.Plugin.Tactic.LanguageServer.TacticProviders.TacticParams
+ Ide.Plugin.Tactic.LanguageServer.TacticProviders: tcCommandId :: TacticCommand -> CommandId
+ Ide.Plugin.Tactic.Simplify: simplify :: LHsExpr GhcPs -> LHsExpr GhcPs
+ Ide.Plugin.Tactic.TestTypes: Config :: FeatureSet -> Config
+ Ide.Plugin.Tactic.TestTypes: [cfg_feature_set] :: Config -> FeatureSet
+ Ide.Plugin.Tactic.TestTypes: emptyConfig :: Config
+ Ide.Plugin.Tactic.TestTypes: instance Data.Aeson.Types.FromJSON.FromJSON Ide.Plugin.Tactic.TestTypes.Config
+ Ide.Plugin.Tactic.TestTypes: instance Data.Aeson.Types.ToJSON.ToJSON Ide.Plugin.Tactic.TestTypes.Config
+ Ide.Plugin.Tactic.TestTypes: newtype Config
+ Ide.Plugin.Tactic.Types: AgdaMatch :: [Pat GhcPs] -> HsExpr GhcPs -> AgdaMatch
+ Ide.Plugin.Tactic.Types: Hypothesis :: [HyInfo a] -> Hypothesis a
+ Ide.Plugin.Tactic.Types: [amBody] :: AgdaMatch -> HsExpr GhcPs
+ Ide.Plugin.Tactic.Types: [amPats] :: AgdaMatch -> [Pat GhcPs]
+ Ide.Plugin.Tactic.Types: [ctxFeatureSet] :: Context -> FeatureSet
+ Ide.Plugin.Tactic.Types: [hi_name] :: HyInfo a -> OccName
+ Ide.Plugin.Tactic.Types: [rtr_ctx] :: RunTacticResults -> Context
+ Ide.Plugin.Tactic.Types: [rtr_jdg] :: RunTacticResults -> Judgement
+ Ide.Plugin.Tactic.Types: [unHypothesis] :: Hypothesis a -> [HyInfo a]
+ Ide.Plugin.Tactic.Types: data AgdaMatch
+ Ide.Plugin.Tactic.Types: instance GHC.Base.Functor Ide.Plugin.Tactic.Types.Hypothesis
+ Ide.Plugin.Tactic.Types: instance GHC.Base.Monoid (Ide.Plugin.Tactic.Types.Hypothesis a)
+ Ide.Plugin.Tactic.Types: instance GHC.Base.Semigroup (Ide.Plugin.Tactic.Types.Hypothesis a)
+ Ide.Plugin.Tactic.Types: instance GHC.Classes.Eq a => GHC.Classes.Eq (Ide.Plugin.Tactic.Types.Hypothesis a)
+ Ide.Plugin.Tactic.Types: instance GHC.Classes.Ord a => GHC.Classes.Ord (Ide.Plugin.Tactic.Types.Hypothesis a)
+ Ide.Plugin.Tactic.Types: instance GHC.Generics.Generic (Ide.Plugin.Tactic.Types.Hypothesis a)
+ Ide.Plugin.Tactic.Types: instance GHC.Show.Show (GHC.Hs.Expr.HsExpr GHC.Hs.Extension.GhcPs)
+ Ide.Plugin.Tactic.Types: instance GHC.Show.Show (GHC.Hs.Pat.Pat GHC.Hs.Extension.GhcPs)
+ Ide.Plugin.Tactic.Types: instance GHC.Show.Show Ide.Plugin.Tactic.Types.AgdaMatch
+ Ide.Plugin.Tactic.Types: instance GHC.Show.Show Ide.Plugin.Tactic.Types.Context
+ Ide.Plugin.Tactic.Types: instance GHC.Show.Show a => GHC.Show.Show (Ide.Plugin.Tactic.Types.Hypothesis a)
+ Ide.Plugin.Tactic.Types: newtype Hypothesis a
- Ide.Plugin.Tactic.CodeGen: destruct' :: (DataCon -> Judgement -> Rule) -> OccName -> Judgement -> Rule
+ Ide.Plugin.Tactic.CodeGen: destruct' :: (DataCon -> Judgement -> Rule) -> HyInfo CType -> Judgement -> Rule
- Ide.Plugin.Tactic.Context: contextMethodHypothesis :: Context -> Map OccName (HyInfo CType)
+ Ide.Plugin.Tactic.Context: contextMethodHypothesis :: Context -> Hypothesis CType
- Ide.Plugin.Tactic.Context: excludeForbiddenMethods :: [(OccName, a)] -> [(OccName, a)]
+ Ide.Plugin.Tactic.Context: excludeForbiddenMethods :: [HyInfo a] -> [HyInfo a]
- Ide.Plugin.Tactic.Context: mkContext :: [(OccName, CType)] -> TcGblEnv -> Context
+ Ide.Plugin.Tactic.Context: mkContext :: FeatureSet -> [(OccName, CType)] -> TcGblEnv -> Context
- Ide.Plugin.Tactic.Judgements: hypothesisFromBindings :: RealSrcSpan -> Bindings -> Map OccName (HyInfo CType)
+ Ide.Plugin.Tactic.Judgements: hypothesisFromBindings :: RealSrcSpan -> Bindings -> Hypothesis CType
- Ide.Plugin.Tactic.Judgements: jEntireHypothesis :: Judgement' a -> Map OccName (HyInfo a)
+ Ide.Plugin.Tactic.Judgements: jEntireHypothesis :: Judgement' a -> Hypothesis a
- Ide.Plugin.Tactic.Judgements: jHypothesis :: Judgement' a -> Map OccName (HyInfo a)
+ Ide.Plugin.Tactic.Judgements: jHypothesis :: Judgement' a -> Hypothesis a
- Ide.Plugin.Tactic.Judgements: jLocalHypothesis :: Judgement' a -> Map OccName (HyInfo a)
+ Ide.Plugin.Tactic.Judgements: jLocalHypothesis :: Judgement' a -> Hypothesis a
- Ide.Plugin.Tactic.Judgements: mkFirstJudgement :: Map OccName (HyInfo CType) -> Bool -> Type -> Judgement' CType
+ Ide.Plugin.Tactic.Judgements: mkFirstJudgement :: Hypothesis CType -> Bool -> Type -> Judgement' CType
- Ide.Plugin.Tactic.Machinery: methodHypothesis :: PredType -> Maybe [(OccName, HyInfo CType)]
+ Ide.Plugin.Tactic.Machinery: methodHypothesis :: PredType -> Maybe [HyInfo CType]
- Ide.Plugin.Tactic.Naming: mkGoodName :: [OccName] -> Type -> OccName
+ Ide.Plugin.Tactic.Naming: mkGoodName :: Set OccName -> Type -> OccName
- Ide.Plugin.Tactic.Naming: mkManyGoodNames :: (Traversable t, Monad m) => Map OccName a -> t Type -> m (t OccName)
+ Ide.Plugin.Tactic.Naming: mkManyGoodNames :: (Traversable t, Monad m) => Set OccName -> t Type -> m (t OccName)
- Ide.Plugin.Tactic.Tactics: allNames :: Judgement -> [OccName]
+ Ide.Plugin.Tactic.Tactics: allNames :: Judgement -> Set OccName
- Ide.Plugin.Tactic.Tactics: apply :: OccName -> TacticsM ()
+ Ide.Plugin.Tactic.Tactics: apply :: HyInfo CType -> TacticsM ()
- Ide.Plugin.Tactic.Tactics: destruct :: OccName -> TacticsM ()
+ Ide.Plugin.Tactic.Tactics: destruct :: HyInfo CType -> TacticsM ()
- Ide.Plugin.Tactic.Tactics: destructAuto :: OccName -> TacticsM ()
+ Ide.Plugin.Tactic.Tactics: destructAuto :: HyInfo CType -> TacticsM ()
- Ide.Plugin.Tactic.Tactics: homo :: OccName -> TacticsM ()
+ Ide.Plugin.Tactic.Tactics: homo :: HyInfo CType -> TacticsM ()
- Ide.Plugin.Tactic.Tactics: overAlgebraicTerms :: (OccName -> TacticsM ()) -> TacticsM ()
+ Ide.Plugin.Tactic.Tactics: overAlgebraicTerms :: (HyInfo CType -> TacticsM ()) -> TacticsM ()
- Ide.Plugin.Tactic.Tactics: overFunctions :: (OccName -> TacticsM ()) -> TacticsM ()
+ Ide.Plugin.Tactic.Tactics: overFunctions :: (HyInfo CType -> TacticsM ()) -> TacticsM ()
- Ide.Plugin.Tactic.Types: Context :: [(OccName, CType)] -> [(OccName, CType)] -> Context
+ Ide.Plugin.Tactic.Types: Context :: [(OccName, CType)] -> [(OccName, CType)] -> FeatureSet -> Context
- Ide.Plugin.Tactic.Types: HyInfo :: Provenance -> a -> HyInfo a
+ Ide.Plugin.Tactic.Types: HyInfo :: OccName -> Provenance -> a -> HyInfo a
- Ide.Plugin.Tactic.Types: Judgement :: !Map OccName (HyInfo a) -> !Bool -> !Bool -> !Bool -> !a -> Judgement' a
+ Ide.Plugin.Tactic.Types: Judgement :: !Hypothesis a -> !Bool -> !Bool -> !Bool -> !a -> Judgement' a
- Ide.Plugin.Tactic.Types: RunTacticResults :: Trace -> LHsExpr GhcPs -> [(Trace, LHsExpr GhcPs)] -> RunTacticResults
+ Ide.Plugin.Tactic.Types: RunTacticResults :: Trace -> LHsExpr GhcPs -> [(Trace, LHsExpr GhcPs)] -> Judgement -> Context -> RunTacticResults
- Ide.Plugin.Tactic.Types: [_jHypothesis] :: Judgement' a -> !Map OccName (HyInfo a)
+ Ide.Plugin.Tactic.Types: [_jHypothesis] :: Judgement' a -> !Hypothesis a

Files

+ README.md view
@@ -0,0 +1,54 @@+<p align="center">+<img src="https://haskellwingman.dev/wingman.png" height="256" alt="Wingman for Haskell" title="Wingman for Haskell">+</p>++<p>&nbsp;</p>++# Wingman for Haskell++[![Hackage](https://img.shields.io/hackage/v/hls-tactics-plugin.svg?logo=haskell&label=hls-tactics-plugin)](https://hackage.haskell.org/package/hls-tactics-plugin)++"Focus on the important stuff; delegate the rest"+++## Dedication++> There's a lot of automation that can happen that isn't a replacement of+> humans, but of mind-numbing behavior.+>+> --Stewart Butterfield+++## Overview++Wingman writes the boring, auxiliary code, so you don't have to. Generate+functions from type signatures, and intelligently complete holes.+++## Getting Started++Wingman for Haskell is enabled by default in all [official release of Haskell+Language Server.][hls] Just hover over a typed hole, run the "Attempt to+fill hole" code action, *et voila!*++[hls]: https://github.com/haskell/haskell-language-server/releases+++## Features++* [Type-directed code synthesis][auto], including pattern matching and recursion+* [Automatic case-splitting][case] --- just run the "Case split on <x>" code action+* [Smart next actions][next], for those times it can't read your mind++[auto]: https://haskellwingman.dev/foldr.gif+[case]: https://haskellwingman.dev/case-split.gif+[next]: https://haskellwingman.dev/intros.gif+++## Support++Please consider [pledging on Patreon][patreon] to support the project and get+access to cutting-edge features.++[patreon]: https://www.patreon.com/wingman_for_haskell+
hls-tactics-plugin.cabal view
@@ -1,17 +1,19 @@ cabal-version:      2.2 category:           Development name:               hls-tactics-plugin-version:            0.5.1.0-synopsis:           Tactics plugin for Haskell Language Server+version:            1.0.0.0+synopsis:           Wingman plugin for Haskell Language Server description:        Please see README.md author:             Sandy Maguire, Reed Mullanix maintainer:         sandy@sandymaguire.me copyright:          Sandy Maguire, Reed Mullanix+homepage:           https://haskellwingman.dev+bug-reports:        https://github.com/haskell/haskell-language-server/issues license:            Apache-2.0 license-file:       LICENSE build-type:         Simple--- extra-source-files:---   README.md+extra-source-files:+  README.md --   ChangeLog.md  flag pedantic@@ -24,23 +26,28 @@   exposed-modules:     Ide.Plugin.Tactic     Ide.Plugin.Tactic.Auto+    Ide.Plugin.Tactic.CaseSplit     Ide.Plugin.Tactic.CodeGen+    Ide.Plugin.Tactic.CodeGen.Utils     Ide.Plugin.Tactic.Context     Ide.Plugin.Tactic.Debug+    Ide.Plugin.Tactic.FeatureSet     Ide.Plugin.Tactic.GHC     Ide.Plugin.Tactic.Judgements     Ide.Plugin.Tactic.KnownStrategies     Ide.Plugin.Tactic.KnownStrategies.QuickCheck+    Ide.Plugin.Tactic.LanguageServer+    Ide.Plugin.Tactic.LanguageServer.TacticProviders     Ide.Plugin.Tactic.Machinery     Ide.Plugin.Tactic.Naming     Ide.Plugin.Tactic.Range+    Ide.Plugin.Tactic.Simplify     Ide.Plugin.Tactic.Tactics     Ide.Plugin.Tactic.Types     Ide.Plugin.Tactic.TestTypes-    Ide.TreeTransform    ghc-options:-    -Wno-name-shadowing -Wredundant-constraints+    -Wno-name-shadowing -Wredundant-constraints -Wno-unticked-promoted-constructors   if flag(pedantic)     ghc-options: -Werror @@ -57,9 +64,9 @@     , ghc-boot-th     , ghc-exactprint     , ghc-source-gen-    , ghcide         >=0.1-    , haskell-lsp    ^>=0.22-    , hls-plugin-api+    , ghcide         ^>= 1.0.0.0+    , lsp+    , hls-plugin-api ^>= 1.0.0.0     , lens     , mtl     , refinery       ^>=0.3@@ -71,27 +78,66 @@     , deepseq    default-language: Haskell2010+  default-extensions: DataKinds, TypeOperators ++executable test-server+  default-language:   Haskell2010+  build-depends:+    , base+    , data-default+    , ghcide+    , hls-tactics-plugin+    , hls-plugin-api+    , shake+  main-is: Server.hs+  hs-source-dirs: test+  ghc-options:+    "-with-rtsopts=-I0 -A128M"+    -threaded -Wall -Wno-name-shadowing -Wredundant-constraints+ test-suite tests   type: exitcode-stdio-1.0   main-is: Main.hs   other-modules:     AutoTupleSpec+    GoldenSpec     UnificationSpec   hs-source-dirs:       test   ghc-options: -Wall -Wredundant-constraints -threaded -rtsopts -with-rtsopts=-N   build-depends:       QuickCheck+    , aeson     , base+    , bytestring     , checkers+    , containers+    , data-default+    , deepseq+    , directory+    , filepath+    , ghc+    , ghcide                >= 0.7.5.0+    , hie-bios+    , hls-plugin-api+    , hls-tactics-plugin     , hspec+    , hspec-expectations+    , lens+    , lsp-test+    , lsp-types+    , megaparsec     , mtl-    , hls-tactics-plugin-    , hls-plugin-api-    , hie-bios-    , ghc-    , containers-  build-tool-depends: hspec-discover:hspec-discover+    , tasty+    , tasty-ant-xml           >=1.1.6+    , tasty-expected-failure+    , tasty-golden+    , tasty-hunit+    , tasty-rerun+    , text+  build-tool-depends:+      hspec-discover:hspec-discover+    , hls-tactics-plugin:test-server -any   default-language: Haskell2010 
src/Ide/Plugin/Tactic.hs view
@@ -1,13 +1,10 @@-{-# LANGUAGE DeriveAnyClass      #-}-{-# LANGUAGE DeriveGeneric       #-}+{-# LANGUAGE FlexibleContexts    #-}+{-# LANGUAGE GADTs               #-} {-# LANGUAGE LambdaCase          #-} {-# LANGUAGE NumDecimals         #-} {-# LANGUAGE OverloadedStrings   #-}-{-# LANGUAGE PatternSynonyms     #-} {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE TupleSections       #-} {-# LANGUAGE TypeApplications    #-}-{-# LANGUAGE ViewPatterns        #-}  -- | A plugin that uses tactics to synthesize code module Ide.Plugin.Tactic@@ -16,52 +13,43 @@   , TacticCommand (..)   ) where -import           Control.Arrow+import           Bag                                              (bagToList,+                                                                   listToBag)+import           Control.Exception                                (evaluate) import           Control.Monad import           Control.Monad.Trans import           Control.Monad.Trans.Maybe import           Data.Aeson-import           Data.Coerce-import           Data.Functor ((<&>))-import           Data.Generics.Aliases (mkQ)-import           Data.Generics.Schemes (everything)-import           Data.List-import           Data.Map (Map)-import qualified Data.Map as M+import           Data.Bifunctor                                   (Bifunctor (bimap))+import           Data.Bool                                        (bool)+import           Data.Data                                        (Data)+import           Data.Generics.Aliases                            (mkQ)+import           Data.Generics.Schemes                            (everything) import           Data.Maybe import           Data.Monoid-import qualified Data.Set as S-import qualified Data.Text as T+import qualified Data.Text                                        as T import           Data.Traversable-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                       (IdeState (..)) import           Development.IDE.GHC.Compat-import           Development.IDE.GHC.Error (realSrcSpanToRange)-import           Development.IDE.Spans.LocalBindings (getDefiningBindings)-import           Development.Shake (Action)-import           DynFlags (xopt)-import qualified FastString-import           GHC.Generics (Generic)-import           GHC.LanguageExtensions.Type (Extension (LambdaCase))-import           Ide.Plugin.Tactic.Auto-import           Ide.Plugin.Tactic.Context+import           Development.IDE.GHC.ExactPrint+import           Development.Shake.Classes+import           Ide.Plugin.Tactic.CaseSplit+import           Ide.Plugin.Tactic.FeatureSet                     (Feature (..),+                                                                   hasFeature) import           Ide.Plugin.Tactic.GHC-import           Ide.Plugin.Tactic.Judgements+import           Ide.Plugin.Tactic.LanguageServer+import           Ide.Plugin.Tactic.LanguageServer.TacticProviders import           Ide.Plugin.Tactic.Range import           Ide.Plugin.Tactic.Tactics import           Ide.Plugin.Tactic.TestTypes import           Ide.Plugin.Tactic.Types-import           Ide.PluginUtils-import           Ide.TreeTransform (transform, graft, useAnnotatedSource) import           Ide.Types-import           Language.Haskell.LSP.Core (clientCapabilities)-import           Language.Haskell.LSP.Types+import           Language.LSP.Server+import           Language.LSP.Types+import           Language.LSP.Types.Capabilities import           OccName-import           SrcLoc (containsSpan)+import           Prelude                                          hiding (span) import           System.Timeout-import           TcRnTypes (tcg_binds)   descriptor :: PluginId -> PluginDescriptor IdeState@@ -73,296 +61,211 @@               (tacticDesc $ tcCommandName tc)               (tacticCmd $ commandTactic tc))               [minBound .. maxBound]-    , pluginCodeActionProvider = Just codeActionProvider+    , pluginHandlers =+        mkPluginHandler STextDocumentCodeAction codeActionProvider     } -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] ----------------------------------------------------------------------------------- | Construct a 'CommandId'-tcCommandId :: TacticCommand -> CommandId-tcCommandId c = coerce $ T.pack $ "tactics" <> show c <> "Command"------------------------------------------------------------------------------------ | The name of the command for the LS.-tcCommandName :: TacticCommand -> T.Text-tcCommandName = T.pack . show----------------------------------------------------------------------------------- | Mapping from tactic commands to their contextual providers. See 'provide',--- 'filterGoalType' and 'filterBindingType' for the nitty gritty.-commandProvider :: TacticCommand -> TacticProvider-commandProvider Auto  = provide Auto ""-commandProvider Intros =-  filterGoalType isFunction $-    provide Intros ""-commandProvider Destruct =-  filterBindingType destructFilter $ \occ _ ->-    provide Destruct $ T.pack $ occNameString occ-commandProvider Homomorphism =-  filterBindingType homoFilter $ \occ _ ->-    provide Homomorphism $ T.pack $ occNameString occ-commandProvider DestructLambdaCase =-  requireExtension LambdaCase $-    filterGoalType (isJust . lambdaCaseable) $-      provide DestructLambdaCase ""-commandProvider HomomorphismLambdaCase =-  requireExtension LambdaCase $-    filterGoalType ((== Just True) . lambdaCaseable) $-      provide HomomorphismLambdaCase ""------------------------------------------------------------------------------------ | 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------------------------------------------------------------------------------------ | We should show homos only when the goal type is the same as the binding--- type, and that both are usual algebraic types.-homoFilter :: Type -> Type -> Bool-homoFilter (algebraicTyCon -> Just t1) (algebraicTyCon -> Just t2) = t1 == t2-homoFilter _ _ = False------------------------------------------------------------------------------------ | We should show destruct for bindings only when those bindings have usual--- algebraic types.-destructFilter :: Type -> Type -> Bool-destructFilter _ (algebraicTyCon -> Just _) = True-destructFilter _ _ = False---runIde :: IdeState -> Action a -> IO a-runIde state = runAction "tactic" state---codeActionProvider :: CodeActionProvider IdeState-codeActionProvider _conf state plId (TextDocumentIdentifier uri) range _ctx-  | Just nfp <- uriToNormalizedFilePath $ toNormalizedUri uri =-      fromMaybeT (Right $ List []) $ do-        (_, jdg, _, dflags) <- judgementForHole state nfp range+codeActionProvider :: PluginMethodHandler IdeState TextDocumentCodeAction+codeActionProvider state plId (CodeActionParams _ _ (TextDocumentIdentifier uri) range _ctx)+  | Just nfp <- uriToNormalizedFilePath $ toNormalizedUri uri = do+      features <- getFeatureSet (shakeExtras state)+      liftIO $ fromMaybeT (Right $ List []) $ do+        (_, jdg, _, dflags) <- judgementForHole state nfp range features         actions <- lift $           -- This foldMap is over the function monoid.           foldMap commandProvider [minBound .. maxBound]             dflags+            features             plId             uri             range             jdg         pure $ Right $ List actions-codeActionProvider _ _ _ _ _ _ = pure $ Right $ codeActions []+codeActionProvider _ _ _ = pure $ Right $ List []  -codeActions :: [CodeAction] -> List CAResult-codeActions = List . fmap CACodeAction+tacticCmd :: (OccName -> TacticsM ()) -> CommandFunction IdeState TacticParams+tacticCmd tac state (TacticParams uri range var_name)+  | Just nfp <- uriToNormalizedFilePath $ toNormalizedUri uri = do+      features <- getFeatureSet (shakeExtras state)+      ccs <- getClientCapabilities+      res <- liftIO $ fromMaybeT (Right Nothing) $ do+        (range', jdg, ctx, dflags) <- judgementForHole state nfp range features+        let span = rangeToRealSrcSpan (fromNormalizedFilePath nfp) range'+        pm <- MaybeT $ useAnnotatedSource "tacticsCmd" state nfp +        timingOut 2e8 $ join $+          bimap (mkErr InvalidRequest . T.pack . show)+                (mkWorkspaceEdits span dflags ccs uri pm)+            $ runTactic ctx jdg $ tac $ mkVarOcc $ T.unpack var_name ---------------------------------------------------------------------------------- | 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-  let title = tacticTitle tc name-      params = TacticParams { file = uri , range = range , var_name = name }-  cmd <- mkLspCommand plId (tcCommandId tc) title (Just [toJSON params])-  pure-    $ pure-    $ CACodeAction-    $ CodeAction title (Just CodeActionQuickFix) Nothing Nothing-    $ Just cmd+      case res of+        Left err -> pure $ Left err+        Right medit -> do+          forM_ medit $ \edit ->+            sendRequest+              SWorkspaceApplyEdit+              (ApplyWorkspaceEditParams Nothing edit)+              (const $ pure ())+          pure $ Right Null+tacticCmd _ _ _ =+  pure $ Left $ mkErr InvalidRequest "Bad URI"  ---------------------------------------------------------------------------------- | 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 =-  case xopt ext dflags of-    True  -> tp dflags plId uri range jdg-    False -> pure []+timingOut+    :: Int                     -- ^ Time in microseconds+    -> Either ResponseError a  -- ^ Computation to run+    -> MaybeT IO (Either ResponseError a)+timingOut t m = do+  x <- lift $ timeout t $ evaluate m+  pure $ joinNote (mkErr InvalidRequest "timed out") x  ---------------------------------------------------------------------------------- | 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 =-  case p $ unCType $ jGoal jdg of-    True  -> tp dflags plId uri range jdg-    False -> pure []+mkErr :: ErrorCode -> T.Text -> ResponseError+mkErr code err = ResponseError code err Nothing  ---------------------------------------------------------------------------------- | Multiply a 'TacticProvider' for each binding, making sure it appears only--- when the given predicate holds over the goal and binding types.-filterBindingType-    :: (Type -> Type -> Bool)  -- ^ Goal and then binding types.-    -> (OccName -> Type -> TacticProvider)-    -> TacticProvider-filterBindingType p tp dflags plId uri range jdg =-  let hy = jHypothesis jdg-      g  = jGoal jdg-   in fmap join $ for (M.toList hy) $ \(occ, hi_type -> CType ty) ->-        case p (unCType g) ty of-          True  -> tp occ ty dflags plId uri range jdg-          False -> pure []+joinNote :: e -> Maybe (Either e a) -> Either e a+joinNote e Nothing  = Left e+joinNote _ (Just a) = a  -data TacticParams = TacticParams-    { file :: Uri -- ^ Uri of the file to fill the hole in-    , range :: Range -- ^ The range of the hole-    , var_name :: T.Text-    }-  deriving (Show, Eq, Generic, ToJSON, FromJSON)+------------------------------------------------------------------------------+-- | Turn a 'RunTacticResults' into concrete edits to make in the source+-- document.+mkWorkspaceEdits+    :: RealSrcSpan+    -> DynFlags+    -> ClientCapabilities+    -> Uri+    -> Annotated ParsedSource+    -> RunTacticResults+    -> Either ResponseError (Maybe WorkspaceEdit)+mkWorkspaceEdits span dflags ccs uri pm rtr = do+  let g = graftHole (RealSrcSpan span) rtr+      response = transform dflags ccs uri g pm+   in case response of+        Right res -> Right $ Just res+        Left err  -> Left $ mkErr InternalError $ T.pack err   --------------------------------------------------------------------------------- | Find the last typechecked module, and find the most specific span, as well--- as the judgement at the given range.-judgementForHole-    :: IdeState-    -> NormalizedFilePath-    -> Range-    -> MaybeT IO (Range, Judgement, Context, DynFlags)-judgementForHole state nfp range = do-  (asts, amapping) <- MaybeT $ runIde state $ useWithStale GetHieAst nfp-  range' <- liftMaybe $ fromCurrentRange amapping range+-- | Graft a 'RunTacticResults' into the correct place in an AST. Correctly+-- deals with top-level holes, in which we might need to fiddle with the+-- 'Match's that bind variables.+graftHole+    :: SrcSpan+    -> RunTacticResults+    -> Graft (Either String) ParsedSource+graftHole span rtr+  | _jIsTopHole (rtr_jdg rtr)+      = graftSmallestDeclsWithM span+      $ graftDecl span $ \pats ->+        splitToDecl (fst $ last $ ctxDefiningFuncs $ rtr_ctx rtr)+      $ iterateSplit+      $ mkFirstAgda (fmap unXPat pats)+      $ unLoc+      $ rtr_extract rtr+graftHole span rtr+  = graftWithoutParentheses span+    -- Parenthesize the extract iff we're not in a top level hole+  $ bool maybeParensAST id (_jIsTopHole $ rtr_jdg rtr)+  $ rtr_extract rtr -  (binds, _) <- MaybeT $ runIde state $ useWithStale GetBindings nfp -  -- Ok to use the stale 'ModIface', since all we need is its 'DynFlags'-  -- which don't change very often.-  ((modsum,_), _) <- MaybeT $ runIde state $ useWithStale GetModSummaryWithoutTimestamps nfp-  let dflags = ms_hspp_opts modsum+------------------------------------------------------------------------------+-- | Merge in the 'Match'es of a 'FunBind' into a 'HsDecl'. Used to perform+-- agda-style case splitting in which we need to separate one 'Match' into+-- many, without affecting any matches which might exist but don't need to be+-- split.+mergeFunBindMatches+    :: ([Pat GhcPs] -> LHsDecl GhcPs)+    -> SrcSpan+    -> HsBind GhcPs+    -> Either String (HsBind GhcPs)+mergeFunBindMatches make_decl span+    (fb@FunBind {fun_matches = mg@MG {mg_alts = L alts_src alts}}) =+  pure $ fb+    { fun_matches = mg+      { mg_alts = L alts_src $ do+          alt@(L alt_src match) <- alts+          case span `isSubspanOf` alt_src of+            True -> do+              let pats = fmap fromPatCompatPs $ m_pats match+                  L _ (ValD _ (FunBind {fun_matches = MG+                        {mg_alts = L _ to_add}})) = make_decl pats+              to_add+            False -> pure alt+      }+    }+mergeFunBindMatches _ _ _ =+  Left "mergeFunBindMatches: called on something that isnt a funbind" -  (rss, goal) <- liftMaybe $ join $ listToMaybe $ M.elems $ flip M.mapWithKey (getAsts $ hieAst asts) $ \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) -  resulting_range <- liftMaybe $ toCurrentRange amapping $ realSrcSpanToRange rss-  (tcmod, _) <- MaybeT $ runIde state $ useWithStale TypeCheck nfp-  let tcg  = tmrTypechecked tcmod-      tcs = tcg_binds tcg-      ctx = mkContext-              (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-  pure ( resulting_range-       , mkFirstJudgement-           (local_hy <> cls_hy)-           (isRhsHole rss tcs)-           goal-       , ctx-       , dflags-       )+throwError :: String -> TransformT (Either String) a+throwError = lift . Left  -spliceProvenance-    :: Map OccName Provenance-    -> Map OccName (HyInfo a)-    -> Map OccName (HyInfo a)-spliceProvenance provs =-  M.mapWithKey $ \name hi ->-    overProvenance (maybe id const $ M.lookup name provs) hi-+------------------------------------------------------------------------------+-- | Helper function to route 'mergeFunBindMatches' into the right place in an+-- AST --- correctly dealing with inserting into instance declarations.+graftDecl+    :: SrcSpan+    -> ([Pat GhcPs] -> LHsDecl GhcPs)+    -> LHsDecl GhcPs+    -> TransformT (Either String) (Maybe [LHsDecl GhcPs])+graftDecl span+    make_decl+    (L src (ValD ext fb))+  = either throwError (pure . Just . pure . L src . ValD ext) $+      mergeFunBindMatches make_decl span fb+-- TODO(sandy): add another case for default methods in class definitions+graftDecl span+    make_decl+    (L src (InstD ext+      cid@ClsInstD{cid_inst =+        cidi@ClsInstDecl{cid_sigs = _sigs, cid_binds = binds}}))+  = do+      binds' <-+        for (bagToList binds) $ \b@(L bsrc bind) -> do+          case bind of+            fb@FunBind{} | span `isSubspanOf` bsrc ->+              either throwError (pure . L bsrc) $+                mergeFunBindMatches make_decl span fb+            _ -> pure b -tacticCmd :: (OccName -> TacticsM ()) -> CommandFunction IdeState TacticParams-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-        let span = rangeToRealSrcSpan (fromNormalizedFilePath nfp) range'-        pm <- MaybeT $ useAnnotatedSource "tacticsCmd" state nfp-        x <- lift $ timeout 2e8 $-          case runTactic ctx jdg-                $ tac-                $ mkVarOcc-                $ T.unpack var_name of-            Left err ->-              pure $ (, Nothing)-                $ Left-                $ ResponseError InvalidRequest (T.pack $ show err) Nothing-            Right rtr -> do-              traceMX "solns" $ rtr_other_solns rtr-              let g = graft (RealSrcSpan span) $ rtr_extract rtr-                  response = transform dflags (clientCapabilities lf) uri g pm-              pure $ case response of-                Right res -> (Right Null , Just (WorkspaceApplyEdit, ApplyWorkspaceEditParams res))-                Left err -> (Left $ ResponseError InternalError (T.pack err) Nothing, Nothing)-        pure $ case x of-          Just y -> y-          Nothing -> (, Nothing)-                   $ Left-                   $ ResponseError InvalidRequest "timed out" Nothing-tacticCmd _ _ _ _ =-  pure ( Left $ ResponseError InvalidRequest (T.pack "Bad URI") Nothing-       , Nothing-       )+      pure $ Just $ pure $ L src $ InstD ext $ cid+        { cid_inst = cidi+          { cid_binds = listToBag binds'+          }+        }+graftDecl span _ x = do+  traceMX "biggest" $+    unsafeRender $+      locateBiggest @(Match GhcPs (LHsExpr GhcPs)) span x+  traceMX "first" $+    unsafeRender $+      locateFirst @(Match GhcPs (LHsExpr GhcPs)) x+  throwError "graftDecl: don't know about this AST form"   fromMaybeT :: Functor m => a -> MaybeT m a -> m a fromMaybeT def = fmap (fromMaybe def) . runMaybeT -liftMaybe :: Monad m => Maybe a -> MaybeT m a-liftMaybe a = MaybeT $ pure a ----------------------------------------------------------------------------------- | 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------------------------------------------------------------------------------------ | 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-+locateBiggest :: (Data r, Data a) => SrcSpan -> a -> Maybe r+locateBiggest ss x = getFirst $ everything (<>)+  ( mkQ mempty $ \case+    L span r | ss `isSubspanOf` span -> pure r+    _                                -> mempty+  ) x  --- TODO(sandy): Make this more robust-isHole :: OccName -> Bool-isHole = isPrefixOf "_" . occNameString+locateFirst :: (Data r, Data a) => a -> Maybe r+locateFirst x = getFirst $ everything (<>)+  ( mkQ mempty $ \case+    r -> pure r+  ) x 
+ src/Ide/Plugin/Tactic/CaseSplit.hs view
@@ -0,0 +1,87 @@+{-# LANGUAGE LambdaCase          #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}+{-# LANGUAGE ViewPatterns        #-}++module Ide.Plugin.Tactic.CaseSplit+  ( mkFirstAgda+  , iterateSplit+  , splitToDecl+  ) where++import           Data.Bool (bool)+import           Data.Data+import           Data.Generics+import           Data.Set (Set)+import qualified Data.Set as S+import           Development.IDE.GHC.Compat+import           GHC.Exts (IsString(fromString))+import           GHC.SourceGen (funBinds, match, wildP)+import           Ide.Plugin.Tactic.GHC+import           Ide.Plugin.Tactic.Types+import           OccName++++------------------------------------------------------------------------------+-- | Construct an 'AgdaMatch' from patterns in scope (should be the LHS of the+-- match) and a body.+mkFirstAgda :: [Pat GhcPs] -> HsExpr GhcPs -> AgdaMatch+mkFirstAgda pats (Lambda pats' body) = mkFirstAgda (pats <> pats') body+mkFirstAgda pats body = AgdaMatch pats body+++------------------------------------------------------------------------------+-- | Transform an 'AgdaMatch' whose body is a case over a bound pattern, by+-- splitting it into multiple matches: one for each alternative of the case.+agdaSplit :: AgdaMatch -> [AgdaMatch]+agdaSplit (AgdaMatch pats (Case (HsVar _ (L _ var)) matches)) = do+  (pat, body) <- matches+  -- TODO(sandy): use an at pattern if necessary+  pure $ AgdaMatch (rewriteVarPat var pat pats) body+agdaSplit x = [x]+++------------------------------------------------------------------------------+-- | Replace unused bound patterns with wild patterns.+wildify :: AgdaMatch -> AgdaMatch+wildify (AgdaMatch pats body) =+  let make_wild = bool id (wildifyT (allOccNames body)) $ not $ containsHole body+   in AgdaMatch (make_wild pats) body+++------------------------------------------------------------------------------+-- | Helper function for 'wildify'.+wildifyT :: Data a => Set OccName -> a -> a+wildifyT (S.map occNameString -> used) = everywhere $ mkT $ \case+  VarPat _ (L _ var) | S.notMember (occNameString $ occName var) used -> wildP+  (x :: Pat GhcPs) -> x+++------------------------------------------------------------------------------+-- | Replace a 'VarPat' with the given @'Pat' GhcPs@.+rewriteVarPat :: Data a => RdrName -> Pat GhcPs -> a -> a+rewriteVarPat name rep = everywhere $ mkT $ \case+  VarPat _ (L _ var) | eqRdrName name var -> rep+  (x :: Pat GhcPs) -> x+++------------------------------------------------------------------------------+-- | Construct an 'HsDecl' from a set of 'AgdaMatch'es.+splitToDecl+    :: OccName  -- ^ The name of the function+    -> [AgdaMatch]+    -> LHsDecl GhcPs+splitToDecl name ams = noLoc $ funBinds (fromString . occNameString . occName $ name) $ do+  AgdaMatch pats body <- ams+  pure $ match pats body+++------------------------------------------------------------------------------+-- | Sometimes 'agdaSplit' exposes another opportunity to do 'agdaSplit'. This+-- function runs it a few times, hoping it will find a fixpoint.+iterateSplit :: AgdaMatch -> [AgdaMatch]+iterateSplit am =+  let iterated = iterate (agdaSplit =<<) $ pure am+   in fmap wildify . head . drop 5 $ iterated+
src/Ide/Plugin/Tactic/CodeGen.hs view
@@ -1,10 +1,12 @@-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE TupleSections    #-}+{-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns     #-} -module Ide.Plugin.Tactic.CodeGen where+module Ide.Plugin.Tactic.CodeGen+  ( module Ide.Plugin.Tactic.CodeGen+  , module Ide.Plugin.Tactic.CodeGen.Utils+  ) where  import           Control.Lens ((+~), (%~), (<>~)) import           Control.Monad.Except@@ -18,7 +20,6 @@ import           DataCon import           Development.IDE.GHC.Compat import           GHC.Exts-import           GHC.SourceGen (RdrNameStr) import           GHC.SourceGen.Binds import           GHC.SourceGen.Expr import           GHC.SourceGen.Overloaded@@ -28,14 +29,14 @@ import           Ide.Plugin.Tactic.Machinery import           Ide.Plugin.Tactic.Naming import           Ide.Plugin.Tactic.Types-import           Name+import           Ide.Plugin.Tactic.CodeGen.Utils import           Type hiding (Var)   useOccName :: MonadState TacticState m => Judgement -> OccName -> m () useOccName jdg name =   -- Only score points if this is in the local hypothesis-  case M.lookup name $ jLocalHypothesis jdg of+  case M.lookup name $ hyByName $ jLocalHypothesis jdg of     Just{}  -> modify              $ (withUsedVals $ S.insert name)              . (field @"ts_unused_top_vals" %~ S.delete name)@@ -75,7 +76,7 @@         [] -> throwError $ GoalMismatch "destruct" g         _ -> fmap unzipTrace $ for dcs $ \dc -> do           let args = dataConInstOrigArgTys' dc apps-          names <- mkManyGoodNames hy args+          names <- mkManyGoodNames (hyNamesInScope hy) args           let hy' = zip names $ coerce args               j = introducingPat scrut dc hy'                 $ withNewGoal g jdg@@ -150,23 +151,20 @@ -- | Combinator for performing case splitting, and running sub-rules on the -- resulting matches. -destruct' :: (DataCon -> Judgement -> Rule) -> OccName -> Judgement -> Rule-destruct' f term jdg = do+destruct' :: (DataCon -> Judgement -> Rule) -> HyInfo CType -> Judgement -> Rule+destruct' f hi jdg = do   when (isDestructBlacklisted jdg) $ throwError NoApplicableTactic-  let hy = jHypothesis jdg-  case M.lookup term hy of-    Nothing -> throwError $ UndefinedHypothesis term-    Just (hi_type -> t) -> do-      useOccName jdg term-      (tr, ms)-          <- destructMatches-               f-               (Just term)-               t-               $ disallowing AlreadyDestructed [term] jdg-      pure ( rose ("destruct " <> show term) $ pure tr-           , noLoc $ case' (var' term) ms-           )+  let term = hi_name hi+  useOccName jdg term+  (tr, ms)+      <- destructMatches+           f+           (Just term)+           (hi_type hi)+           $ disallowing AlreadyDestructed [term] jdg+  pure ( rose ("destruct " <> show term) $ pure tr+       , noLoc $ case' (var' term) ms+       )   ------------------------------------------------------------------------------@@ -178,7 +176,7 @@   let g  = jGoal jdg   case splitFunTy_maybe (unCType g) of     Just (arg, _) | isAlgType arg ->-      fmap (fmap noLoc $ lambdaCase) <$>+      fmap (fmap noLoc lambdaCase) <$>         destructMatches f Nothing (CType arg) jdg     _ -> throwError $ GoalMismatch "destructLambdaCase'" g @@ -190,8 +188,8 @@     -> DataCon            -- ^ The data con to build     -> [Type]             -- ^ Type arguments for the data con     -> RuleM (Trace, LHsExpr GhcPs)-buildDataCon jdg dc apps = do-  let args = dataConInstOrigArgTys' dc apps+buildDataCon jdg dc tyapps = do+  let args = dataConInstOrigArgTys' dc tyapps   (tr, sgs)       <- fmap unzipTrace        $ traverse ( \(arg, n) ->@@ -204,59 +202,4 @@   pure     . (rose (show dc) $ pure tr,)     $ mkCon dc sgs---mkCon :: DataCon -> [LHsExpr GhcPs] -> LHsExpr GhcPs-mkCon dcon (fmap unLoc -> args)-  | isTupleDataCon dcon =-      noLoc $ tuple args-  | dataConIsInfix dcon-  , (lhs : rhs : args') <- args =-      noLoc $ foldl' (@@) (op lhs (coerceName dcon_name) rhs) args'-  | otherwise =-      noLoc $ foldl' (@@) (bvar' $ occName $ dcon_name) args-  where-    dcon_name = dataConName dcon----coerceName :: HasOccName a => a -> RdrNameStr-coerceName = fromString . occNameString . occName------------------------------------------------------------------------------------- | Like 'var', but works over standard GHC 'OccName's.-var' :: Var a => OccName -> a-var' = var . fromString . occNameString------------------------------------------------------------------------------------ | Like 'bvar', but works over standard GHC 'OccName's.-bvar' :: BVar a => OccName -> a-bvar' = bvar . fromString . occNameString------------------------------------------------------------------------------------ | Get an HsExpr corresponding to a function name.-mkFunc :: String -> HsExpr GhcPs-mkFunc = var' . mkVarOcc------------------------------------------------------------------------------------ | Get an HsExpr corresponding to a value name.-mkVal :: String -> HsExpr GhcPs-mkVal = var' . mkVarOcc------------------------------------------------------------------------------------ | Like 'op', but easier to call.-infixCall :: String -> HsExpr GhcPs -> HsExpr GhcPs -> HsExpr GhcPs-infixCall s = flip op (fromString s)------------------------------------------------------------------------------------ | Like '(@@)', but uses a dollar instead of parentheses.-appDollar :: HsExpr GhcPs -> HsExpr GhcPs -> HsExpr GhcPs-appDollar = infixCall "$" 
+ src/Ide/Plugin/Tactic/CodeGen/Utils.hs view
@@ -0,0 +1,72 @@+{-# LANGUAGE ViewPatterns #-}++module Ide.Plugin.Tactic.CodeGen.Utils where++import Data.List+import DataCon+import Development.IDE.GHC.Compat+import GHC.Exts+import GHC.SourceGen (recordConE, RdrNameStr)+import GHC.SourceGen.Overloaded+import Ide.Plugin.Tactic.GHC (getRecordFields)+import Name+++------------------------------------------------------------------------------+-- | Make a data constructor with the given arguments.+mkCon :: DataCon -> [LHsExpr GhcPs] -> LHsExpr GhcPs+mkCon dcon (fmap unLoc -> args)+  | isTupleDataCon dcon =+      noLoc $ tuple args+  | dataConIsInfix dcon+  , (lhs : rhs : args') <- args =+      noLoc $ foldl' (@@) (op lhs (coerceName dcon_name) rhs) args'+  | Just fields <- getRecordFields dcon =+      noLoc $ recordConE (coerceName dcon_name) $ do+        (arg, (field, _)) <- zip args fields+        pure (coerceName field, arg)+  | otherwise =+      noLoc $ foldl' (@@) (bvar' $ occName dcon_name) args+  where+    dcon_name = dataConName dcon+++coerceName :: HasOccName a => a -> RdrNameStr+coerceName = fromString . occNameString . occName+++------------------------------------------------------------------------------+-- | Like 'var', but works over standard GHC 'OccName's.+var' :: Var a => OccName -> a+var' = var . fromString . occNameString+++------------------------------------------------------------------------------+-- | Like 'bvar', but works over standard GHC 'OccName's.+bvar' :: BVar a => OccName -> a+bvar' = bvar . fromString . occNameString+++------------------------------------------------------------------------------+-- | Get an HsExpr corresponding to a function name.+mkFunc :: String -> HsExpr GhcPs+mkFunc = var' . mkVarOcc+++------------------------------------------------------------------------------+-- | Get an HsExpr corresponding to a value name.+mkVal :: String -> HsExpr GhcPs+mkVal = var' . mkVarOcc+++------------------------------------------------------------------------------+-- | Like 'op', but easier to call.+infixCall :: String -> HsExpr GhcPs -> HsExpr GhcPs -> HsExpr GhcPs+infixCall s = flip op (fromString s)+++------------------------------------------------------------------------------+-- | Like '(@@)', but uses a dollar instead of parentheses.+appDollar :: HsExpr GhcPs -> HsExpr GhcPs -> HsExpr GhcPs+appDollar = infixCall "$"+
src/Ide/Plugin/Tactic/Context.hs view
@@ -7,8 +7,6 @@ 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@@ -20,24 +18,26 @@ import           TcRnTypes import           TcType (substTy, tcSplitSigmaTy) import           Unify (tcUnifyTy)+import Ide.Plugin.Tactic.FeatureSet (FeatureSet)  -mkContext :: [(OccName, CType)] -> TcGblEnv -> Context-mkContext locals tcg = Context+mkContext :: FeatureSet -> [(OccName, CType)] -> TcGblEnv -> Context+mkContext features locals tcg = Context   { ctxDefiningFuncs = locals   , ctxModuleFuncs = fmap splitId                    . (getFunBindId =<<)                    . fmap unLoc                    . bagToList                    $ tcg_binds tcg+  , ctxFeatureSet = features   }   ------------------------------------------------------------------------------ -- | Find all of the class methods that exist from the givens in the context.-contextMethodHypothesis :: Context -> Map OccName (HyInfo CType)+contextMethodHypothesis :: Context -> Hypothesis CType contextMethodHypothesis ctx-  = M.fromList+  = Hypothesis   . excludeForbiddenMethods   . join   . concatMap@@ -54,8 +54,8 @@ -- | 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)+excludeForbiddenMethods :: [HyInfo a] -> [HyInfo a]+excludeForbiddenMethods = filter (not . flip S.member forbiddenMethods . hi_name)   where     forbiddenMethods :: Set OccName     forbiddenMethods = S.map mkVarOcc $ S.fromList@@ -89,8 +89,7 @@   getCurrentDefinitions :: MonadReader Context m => m [(OccName, CType)]-getCurrentDefinitions = asks $ ctxDefiningFuncs+getCurrentDefinitions = asks ctxDefiningFuncs  getModuleHypothesis :: MonadReader Context m => m [(OccName, CType)] getModuleHypothesis = asks ctxModuleFuncs-
src/Ide/Plugin/Tactic/Debug.hs view
@@ -11,6 +11,7 @@   , traceX   , traceIdX   , traceMX+  , traceFX   ) where  import Control.DeepSeq@@ -52,4 +53,7 @@  traceIdX :: (Show a) => String -> a -> a traceIdX str a = trace (mappend ("!!!" <> str <> ": ") $ show a) a++traceFX :: String -> (a -> String) -> a -> a+traceFX str f a = trace (mappend ("!!!" <> str <> ": ") $ f a) a 
+ src/Ide/Plugin/Tactic/FeatureSet.hs view
@@ -0,0 +1,92 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE PatternSynonyms   #-}+{-# LANGUAGE ViewPatterns      #-}++module Ide.Plugin.Tactic.FeatureSet+  ( Feature (..)+  , FeatureSet+  , hasFeature+  , defaultFeatures+  , allFeatures+  , parseFeatureSet+  , prettyFeatureSet+  ) where++import           Data.List (intercalate)+import           Data.Maybe (mapMaybe, listToMaybe)+import           Data.Set (Set)+import qualified Data.Set as S+import qualified Data.Text as T+++------------------------------------------------------------------------------+-- | All the available features. A 'FeatureSet' describes the ones currently+-- available to the user.+data Feature = CantHaveAnEmptyDataType+  deriving (Eq, Ord, Show, Read, Enum, Bounded)+++------------------------------------------------------------------------------+-- | A collection of enabled features.+type FeatureSet = Set Feature+++------------------------------------------------------------------------------+-- | Parse a feature set.+parseFeatureSet :: T.Text -> FeatureSet+parseFeatureSet+  = mappend defaultFeatures+  . S.fromList+  . mapMaybe (readMaybe . mappend featurePrefix . rot13 . T.unpack)+  . T.split (== '/')+++------------------------------------------------------------------------------+-- | Features that are globally enabled for all users.+defaultFeatures :: FeatureSet+defaultFeatures = S.fromList+  [+  ]+++------------------------------------------------------------------------------+-- | All available features.+allFeatures :: FeatureSet+allFeatures = S.fromList $ enumFromTo minBound maxBound+++------------------------------------------------------------------------------+-- | Pretty print a feature set.+prettyFeatureSet :: FeatureSet -> String+prettyFeatureSet+  = intercalate "/"+  . fmap (rot13 . drop (length featurePrefix) . show)+  . S.toList+++------------------------------------------------------------------------------+-- | Is a given 'Feature' currently enabled?+hasFeature :: Feature -> FeatureSet -> Bool+hasFeature = S.member+++------------------------------------------------------------------------------+-- | Like 'read', but not partial.+readMaybe :: Read a => String -> Maybe a+readMaybe = fmap fst . listToMaybe . reads+++featurePrefix :: String+featurePrefix = "Feature"+++rot13 :: String -> String+rot13 = fmap (toEnum . rot13int . fromEnum)+++rot13int :: Integral a => a -> a+rot13int x+  | (fromIntegral x :: Word) - 97 < 26 = 97 + rem (x - 84) 26+  | (fromIntegral x :: Word) - 65 < 26 = 65 + rem (x - 52) 26+  | otherwise   = x+
src/Ide/Plugin/Tactic/GHC.hs view
@@ -1,17 +1,26 @@-{-# LANGUAGE CPP              #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE PatternSynonyms  #-}-{-# LANGUAGE ViewPatterns     #-}+{-# LANGUAGE CPP                 #-}+{-# LANGUAGE FlexibleContexts    #-}+{-# LANGUAGE LambdaCase          #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE PatternSynonyms     #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE TypeApplications    #-}+{-# LANGUAGE ViewPatterns        #-}  module Ide.Plugin.Tactic.GHC where  import           Control.Monad.State+import           Data.Function (on)+import           Data.List (isPrefixOf) import qualified Data.Map as M import           Data.Maybe (isJust)+import           Data.Set (Set)+import qualified Data.Set as S import           Data.Traversable-import qualified DataCon as DataCon+import           DataCon import           Development.IDE.GHC.Compat-import           Generics.SYB (mkT, everywhere)+import           GHC.SourceGen (match, case', lambda)+import           Generics.SYB (mkQ, everything, listify, Data, mkT, everywhere) import           Ide.Plugin.Tactic.Types import           OccName import           TcType@@ -21,6 +30,7 @@ import           Unique import           Var + tcTyVar_maybe :: Type -> Maybe Var tcTyVar_maybe ty | Just ty' <- tcView ty = tcTyVar_maybe ty' tcTyVar_maybe (CastTy ty _) = tcTyVar_maybe ty  -- look through casts, as@@ -78,7 +88,7 @@   reps <- fmap M.fromList         $ for tvs $ \tv -> do             uniq <- freshUnique-            pure $ (tv, setTyVarUnique tv uniq)+            pure (tv, setTyVarUnique tv uniq)   pure $     everywhere       (mkT $ \tv ->@@ -89,6 +99,18 @@   ------------------------------------------------------------------------------+-- | Given a datacon, extract its record fields' names and types. Returns+-- nothing if the datacon is not a record.+getRecordFields :: DataCon -> Maybe [(OccName, CType)]+getRecordFields dc =+  case dataConFieldLabels dc of+    [] -> Nothing+    lbls -> for lbls $ \lbl -> do+      (_, ty) <- dataConFieldType_maybe dc $ flLabel lbl+      pure (mkVarOccFS $ flLabel lbl, CType ty)+++------------------------------------------------------------------------------ -- | Is this an algebraic type? algebraicTyCon :: Type -> Maybe TyCon algebraicTyCon (splitTyConApp_maybe -> Just (tycon, _))@@ -100,7 +122,103 @@   | otherwise = Just tycon algebraicTyCon _ = Nothing + ------------------------------------------------------------------------------+-- | We can't compare 'RdrName' for equality directly. Instead, compare them by+-- their 'OccName's.+eqRdrName :: RdrName -> RdrName -> Bool+eqRdrName = (==) `on` occNameString . occName+++------------------------------------------------------------------------------+-- | Does this thing contain any references to 'HsVar's with the given+-- 'RdrName'?+containsHsVar :: Data a => RdrName -> a -> Bool+containsHsVar name x = not $ null $ listify (+  \case+    ((HsVar _ (L _ a)) :: HsExpr GhcPs) | eqRdrName a name -> True+    _ -> False+  ) x+++------------------------------------------------------------------------------+-- | Does this thing contain any holes?+containsHole :: Data a => a -> Bool+containsHole x = not $ null $ listify (+  \case+    ((HsVar _ (L _ name)) :: HsExpr GhcPs) -> isHole $ occName name+    _ -> False+  ) x+++------------------------------------------------------------------------------+-- | Check if an 'OccName' is a hole+isHole :: OccName -> Bool+-- TODO(sandy): Make this more robust+isHole = isPrefixOf "_" . occNameString+++------------------------------------------------------------------------------+-- | Get all of the referenced occnames.+allOccNames :: Data a => a -> Set OccName+allOccNames = everything (<>) $ mkQ mempty $ \case+    a -> S.singleton a+++++------------------------------------------------------------------------------+-- | A pattern over the otherwise (extremely) messy AST for lambdas.+pattern Lambda :: [Pat GhcPs] -> HsExpr GhcPs -> HsExpr GhcPs+pattern Lambda pats body <-+  HsLam _+    (MG {mg_alts = L _ [L _+      (Match { m_pats = fmap fromPatCompatPs -> pats+             , m_grhss = UnguardedRHSs body+             })]})+  where+    -- If there are no patterns to bind, just stick in the body+    Lambda [] body   = body+    Lambda pats body = lambda pats body+++------------------------------------------------------------------------------+-- | A GRHS that caontains no guards.+pattern UnguardedRHSs :: HsExpr GhcPs -> GRHSs GhcPs (LHsExpr GhcPs)+pattern UnguardedRHSs body <-+  GRHSs {grhssGRHSs = [L _ (GRHS _ [] (L _ body))]}+++------------------------------------------------------------------------------+-- | A match with a single pattern. Case matches are always 'SinglePatMatch'es.+pattern SinglePatMatch :: Pat GhcPs -> HsExpr GhcPs -> Match GhcPs (LHsExpr GhcPs)+pattern SinglePatMatch pat body <-+  Match { m_pats = [fromPatCompatPs -> pat]+        , m_grhss = UnguardedRHSs body+        }+++------------------------------------------------------------------------------+-- | Helper function for defining the 'Case' pattern.+unpackMatches :: [Match GhcPs (LHsExpr GhcPs)] -> Maybe [(Pat GhcPs, HsExpr GhcPs)]+unpackMatches [] = Just []+unpackMatches (SinglePatMatch pat body : matches) =+  (:) <$> pure (pat, body) <*> unpackMatches matches+unpackMatches _ = Nothing+++------------------------------------------------------------------------------+-- | A pattern over the otherwise (extremely) messy AST for lambdas.+pattern Case :: HsExpr GhcPs -> [(Pat GhcPs, HsExpr GhcPs)] -> HsExpr GhcPs+pattern Case scrutinee matches <-+  HsCase _ (L _ scrutinee)+    (MG {mg_alts = L _ (fmap unLoc -> unpackMatches -> Just matches)})+  where+    Case scrutinee matches =+      case' scrutinee $ fmap (\(pat, body) -> match [pat] body) matches+++------------------------------------------------------------------------------ -- | Can ths type be lambda-cased? -- -- Return: 'Nothing' if no@@ -112,13 +230,17 @@   = Just $ isJust $ algebraicTyCon res lambdaCaseable _ = Nothing -fromPatCompat :: PatCompat GhcTc -> Pat GhcTc+-- It's hard to generalize over these since weird type families are involved.+fromPatCompatTc :: PatCompat GhcTc -> Pat GhcTc+fromPatCompatPs :: PatCompat GhcPs -> Pat GhcPs #if __GLASGOW_HASKELL__ == 808 type PatCompat pass = Pat pass-fromPatCompat = id+fromPatCompatTc = id+fromPatCompatPs = id #else type PatCompat pass = LPat pass-fromPatCompat = unLoc+fromPatCompatTc = unLoc+fromPatCompatPs = unLoc #endif  ------------------------------------------------------------------------------@@ -132,7 +254,7 @@       [L _ (GRHS _ [] body)] _)  getPatName :: PatCompat GhcTc -> Maybe OccName-getPatName (fromPatCompat -> p0) =+getPatName (fromPatCompatTc -> p0) =   case p0 of     VarPat  _ x   -> Just $ occName $ unLoc x     LazyPat _ p   -> getPatName p@@ -154,3 +276,17 @@ #else dataConExTys = DataCon.dataConExTyVars #endif+++------------------------------------------------------------------------------+-- | In GHC 8.8, sometimes patterns are wrapped in 'XPat'.+-- The nitty gritty details are explained at+-- https://blog.shaynefletcher.org/2020/03/ghc-haskell-pats-and-lpats.html+--+-- We need to remove these in order to succesfull find patterns.+unXPat :: Pat GhcPs -> Pat GhcPs+#if __GLASGOW_HASKELL__ == 808+unXPat (XPat (L _ pat)) = unXPat pat+#endif+unXPat pat = pat+
src/Ide/Plugin/Tactic/Judgements.hs view
@@ -1,7 +1,3 @@-{-# LANGUAGE DataKinds        #-}-{-# LANGUAGE LambdaCase       #-}-{-# LANGUAGE RecordWildCards  #-}-{-# LANGUAGE TupleSections    #-} {-# LANGUAGE TypeApplications #-} {-# LANGUAGE ViewPatterns     #-} @@ -29,8 +25,11 @@   , mkFirstJudgement   , hypothesisFromBindings   , isTopLevel+  , hyNamesInScope+  , hyByName   ) where +import           Control.Arrow import           Control.Lens hiding (Context) import           Data.Bool import           Data.Char@@ -51,20 +50,20 @@  ------------------------------------------------------------------------------ -- | Given a 'SrcSpan' and a 'Bindings', create a hypothesis.-hypothesisFromBindings :: RealSrcSpan -> Bindings -> Map OccName (HyInfo CType)+hypothesisFromBindings :: RealSrcSpan -> Bindings -> Hypothesis CType hypothesisFromBindings span bs = buildHypothesis $ getLocalScope bs span   ------------------------------------------------------------------------------ -- | Convert a @Set Id@ into a hypothesis.-buildHypothesis :: [(Name, Maybe Type)] -> Map OccName (HyInfo CType)+buildHypothesis :: [(Name, Maybe Type)] -> Hypothesis CType buildHypothesis-  = M.fromList+  = Hypothesis   . mapMaybe go   where     go (occName -> occ, t)       | Just ty <- t-      , isAlpha . head . occNameString $ occ = Just (occ, HyInfo UserPrv $ CType ty)+      , isAlpha . head . occNameString $ occ = Just $ HyInfo occ UserPrv $ CType ty       | otherwise = Nothing  @@ -99,8 +98,8 @@     -> Judgement' a     -> Judgement' a introducing f ns =-  field @"_jHypothesis" <>~ M.fromList (zip [0..] ns <&>-    \(pos, (name, ty)) -> (name, HyInfo (f pos) ty))+  field @"_jHypothesis" <>~ (Hypothesis $ zip [0..] ns <&>+    \(pos, (name, ty)) -> HyInfo name (f pos) ty)   ------------------------------------------------------------------------------@@ -152,7 +151,7 @@     -> Judgement     -> Judgement filterAncestry ancestry reason jdg =-    disallowing reason (M.keys $ M.filterWithKey go $ jHypothesis jdg) jdg+    disallowing reason (M.keys $ M.filterWithKey go $ hyByName $ jHypothesis jdg) jdg   where     go name _       = not@@ -175,7 +174,7 @@ findPositionVal jdg defn pos = listToMaybe $ do   -- It's important to inspect the entire hypothesis here, as we need to trace   -- ancstry through potentially disallowed terms in the hypothesis.-  (name, hi) <- M.toList $ M.map (overProvenance expandDisallowed) $ jEntireHypothesis jdg+  (name, hi) <- M.toList $ M.map (overProvenance expandDisallowed) $ hyByName $ jEntireHypothesis jdg   case hi_provenance hi of     TopLevelArgPrv defn' pos'       | defn == defn'@@ -191,7 +190,7 @@ -- 'filterSameTypeFromOtherPositions'. findDconPositionVals :: Judgement' a -> DataCon -> Int -> [OccName] findDconPositionVals jdg dcon pos = do-  (name, hi) <- M.toList $ jHypothesis jdg+  (name, hi) <- M.toList $ hyByName $ jHypothesis jdg   case hi_provenance hi of     PatternMatchPrv pv       | pv_datacon  pv == Uniquely dcon@@ -206,14 +205,15 @@ -- other term which might match. filterSameTypeFromOtherPositions :: DataCon -> Int -> Judgement -> Judgement filterSameTypeFromOtherPositions dcon pos jdg =-  let hy = jHypothesis+  let hy = hyByName+         . jHypothesis          $ filterAncestry              (findDconPositionVals jdg dcon pos)              (WrongBranch pos)              jdg       tys = S.fromList $ hi_type <$> M.elems hy       to_remove =-        M.filter (flip S.member tys . hi_type) (jHypothesis jdg)+        M.filter (flip S.member tys . hi_type) (hyByName $ jHypothesis jdg)           M.\\ hy    in disallowing Shadowed (M.keys to_remove) jdg @@ -270,8 +270,8 @@ -- them from 'jHypothesis', but not from 'jEntireHypothesis'. disallowing :: DisallowReason -> [OccName] -> Judgement' a -> Judgement' a disallowing reason (S.fromList -> ns) =-  field @"_jHypothesis" %~ (M.mapWithKey $ \name hi ->-    case S.member name ns of+  field @"_jHypothesis" %~ (\z -> Hypothesis . flip fmap (unHypothesis z) $ \hi ->+    case S.member (hi_name hi) ns of       True -> overProvenance (DisallowedPrv reason) hi       False -> hi                            )@@ -280,20 +280,28 @@ ------------------------------------------------------------------------------ -- | The hypothesis, consisting of local terms and the ambient environment -- (impors and class methods.) Hides disallowed values.-jHypothesis :: Judgement' a -> Map OccName (HyInfo a)-jHypothesis = M.filter (not . isDisallowed . hi_provenance) . jEntireHypothesis+jHypothesis :: Judgement' a -> Hypothesis a+jHypothesis+  = Hypothesis+  . filter (not . isDisallowed . hi_provenance)+  . unHypothesis+  . jEntireHypothesis   ------------------------------------------------------------------------------ -- | The whole hypothesis, including things disallowed.-jEntireHypothesis :: Judgement' a -> Map OccName (HyInfo a)+jEntireHypothesis :: Judgement' a -> Hypothesis a jEntireHypothesis = _jHypothesis   ------------------------------------------------------------------------------ -- | Just the local hypothesis.-jLocalHypothesis :: Judgement' a -> Map OccName (HyInfo a)-jLocalHypothesis = M.filter (isLocalHypothesis . hi_provenance) . jHypothesis+jLocalHypothesis :: Judgement' a -> Hypothesis a+jLocalHypothesis+  = Hypothesis+  . filter (isLocalHypothesis . hi_provenance)+  . unHypothesis+  . jHypothesis   ------------------------------------------------------------------------------@@ -308,9 +316,29 @@   ------------------------------------------------------------------------------+-- | What names are currently in scope in the hypothesis?+hyNamesInScope :: Hypothesis a -> Set OccName+hyNamesInScope = M.keysSet . hyByName+++------------------------------------------------------------------------------+-- | Fold a hypothesis into a single mapping from name to info. This+-- unavoidably will cause duplicate names (things like methods) to shadow one+-- another.+hyByName :: Hypothesis a -> Map OccName (HyInfo a)+hyByName+  = M.fromList+  . fmap (hi_name &&& id)+  . unHypothesis+++------------------------------------------------------------------------------ -- | Only the hypothesis members which are pattern vals jPatHypothesis :: Judgement' a -> Map OccName PatVal-jPatHypothesis = M.mapMaybe (getPatVal . hi_provenance) . jHypothesis+jPatHypothesis+  = M.mapMaybe (getPatVal . hi_provenance)+  . hyByName+  . jHypothesis   getPatVal :: Provenance-> Maybe PatVal@@ -329,7 +357,7 @@   mkFirstJudgement-    :: M.Map OccName (HyInfo CType)+    :: Hypothesis CType     -> Bool  -- ^ are we in the top level rhs hole?     -> Type     -> Judgement' CType@@ -377,4 +405,3 @@ expandDisallowed :: Provenance -> Provenance expandDisallowed (DisallowedPrv _ prv) = expandDisallowed prv expandDisallowed prv = prv-
src/Ide/Plugin/Tactic/KnownStrategies/QuickCheck.hs view
@@ -1,5 +1,4 @@ {-# LANGUAGE ViewPatterns #-}-{-# LANGUAGE LambdaCase #-}  module Ide.Plugin.Tactic.KnownStrategies.QuickCheck where @@ -109,4 +108,3 @@                     (n == 1)             @@ arbitrary         False -> arbitrary-
+ src/Ide/Plugin/Tactic/LanguageServer.hs view
@@ -0,0 +1,212 @@+{-# 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+
+ src/Ide/Plugin/Tactic/LanguageServer/TacticProviders.hs view
@@ -0,0 +1,192 @@+{-# LANGUAGE DeriveAnyClass     #-}+{-# LANGUAGE DeriveGeneric      #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE OverloadedStrings  #-}+{-# LANGUAGE ViewPatterns       #-}++module Ide.Plugin.Tactic.LanguageServer.TacticProviders+  ( commandProvider+  , commandTactic+  , tcCommandId+  , TacticParams (..)+  ) where++import           Control.Monad+import           Control.Monad.Error.Class (MonadError(throwError))+import           Data.Aeson+import           Data.Coerce+import qualified Data.Map as M+import           Data.Maybe+import           Data.Monoid+import qualified Data.Text as T+import           Data.Traversable+import           Development.IDE.GHC.Compat+import           GHC.Generics+import           GHC.LanguageExtensions.Type (Extension (LambdaCase))+import           Ide.Plugin.Tactic.Auto+import           Ide.Plugin.Tactic.FeatureSet+import           Ide.Plugin.Tactic.GHC+import           Ide.Plugin.Tactic.Judgements+import           Ide.Plugin.Tactic.Tactics+import           Ide.Plugin.Tactic.TestTypes+import           Ide.Plugin.Tactic.Types+import           Ide.PluginUtils+import           Ide.Types+import           Language.LSP.Types+import           OccName+import           Prelude hiding (span)+import           Refinery.Tactic (goal)+++------------------------------------------------------------------------------+-- | A mapping from tactic commands to actual tactics for refinery.+commandTactic :: TacticCommand -> OccName -> TacticsM ()+commandTactic Auto         = const auto+commandTactic Intros       = const intros+commandTactic Destruct     = useNameFromHypothesis destruct+commandTactic Homomorphism = useNameFromHypothesis homo+commandTactic DestructLambdaCase     = const destructLambdaCase+commandTactic HomomorphismLambdaCase = const homoLambdaCase+++------------------------------------------------------------------------------+-- | Mapping from tactic commands to their contextual providers. See 'provide',+-- 'filterGoalType' and 'filterBindingType' for the nitty gritty.+commandProvider :: TacticCommand -> TacticProvider+commandProvider Auto  = provide Auto ""+commandProvider Intros =+  filterGoalType isFunction $+    provide Intros ""+commandProvider Destruct =+  filterBindingType destructFilter $ \occ _ ->+    provide Destruct $ T.pack $ occNameString occ+commandProvider Homomorphism =+  filterBindingType homoFilter $ \occ _ ->+    provide Homomorphism $ T.pack $ occNameString occ+commandProvider DestructLambdaCase =+  requireExtension LambdaCase $+    filterGoalType (isJust . lambdaCaseable) $+      provide DestructLambdaCase ""+commandProvider HomomorphismLambdaCase =+  requireExtension LambdaCase $+    filterGoalType ((== Just True) . lambdaCaseable) $+      provide HomomorphismLambdaCase ""+++------------------------------------------------------------------------------+-- | A 'TacticProvider' is a way of giving context-sensitive actions to the LS+-- UI.+type TacticProvider+     = DynFlags+    -> FeatureSet+    -> PluginId+    -> Uri+    -> Range+    -> Judgement+    -> IO [Command |? CodeAction]+++data TacticParams = TacticParams+    { tp_file     :: Uri    -- ^ Uri of the file to fill the hole in+    , tp_range    :: Range  -- ^ The range of the hole+    , tp_var_name :: T.Text+    }+  deriving stock (Show, Eq, Generic)+  deriving anyclass (ToJSON, FromJSON)+++------------------------------------------------------------------------------+-- | Restrict a 'TacticProvider', making sure it appears only when the given+-- 'Feature' is in the feature set.+requireFeature :: Feature -> TacticProvider -> TacticProvider+requireFeature f tp dflags fs plId uri range jdg = do+  guard $ hasFeature f fs+  tp dflags fs plId uri range jdg+++------------------------------------------------------------------------------+-- | Restrict a 'TacticProvider', making sure it appears only when the given+-- predicate holds for the goal.+requireExtension :: Extension -> TacticProvider -> TacticProvider+requireExtension ext tp dflags fs plId uri range jdg =+  case xopt ext dflags of+    True  -> tp dflags fs 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 fs plId uri range jdg =+  case p $ unCType $ jGoal jdg of+    True  -> tp dflags fs plId uri range jdg+    False -> pure []+++------------------------------------------------------------------------------+-- | Multiply a 'TacticProvider' for each binding, making sure it appears only+-- when the given predicate holds over the goal and binding types.+filterBindingType+    :: (Type -> Type -> Bool)  -- ^ Goal and then binding types.+    -> (OccName -> Type -> TacticProvider)+    -> TacticProvider+filterBindingType p tp dflags fs plId uri range jdg =+  let hy = jHypothesis jdg+      g  = jGoal jdg+   in fmap join $ for (unHypothesis hy) $ \hi ->+        let ty = unCType $ hi_type hi+         in case p (unCType g) ty of+              True  -> tp (hi_name hi) ty dflags fs plId uri range jdg+              False -> pure []++++------------------------------------------------------------------------------+-- | Lift a function over 'HyInfo's to one that takes an 'OccName' and tries to+-- look it up in the hypothesis.+useNameFromHypothesis :: (HyInfo CType -> TacticsM a) -> OccName -> TacticsM a+useNameFromHypothesis f name = do+  hy <- jHypothesis <$> goal+  case M.lookup name $ hyByName hy of+    Just hi -> f hi+    Nothing -> throwError $ NotInScope name+++------------------------------------------------------------------------------+-- | 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+  let title = tacticTitle tc name+      params = TacticParams { tp_file = uri , tp_range = range , tp_var_name = name }+      cmd = mkLspCommand plId (tcCommandId tc) title (Just [toJSON params])+  pure+    $ pure+    $ InR+    $ CodeAction title (Just CodeActionQuickFix) Nothing Nothing Nothing Nothing+    $ Just cmd+++------------------------------------------------------------------------------+-- | Construct a 'CommandId'+tcCommandId :: TacticCommand -> CommandId+tcCommandId c = coerce $ T.pack $ "tactics" <> show c <> "Command"++++------------------------------------------------------------------------------+-- | We should show homos only when the goal type is the same as the binding+-- type, and that both are usual algebraic types.+homoFilter :: Type -> Type -> Bool+homoFilter (algebraicTyCon -> Just t1) (algebraicTyCon -> Just t2) = t1 == t2+homoFilter _ _ = False+++------------------------------------------------------------------------------+-- | We should show destruct for bindings only when those bindings have usual+-- algebraic types.+destructFilter :: Type -> Type -> Bool+destructFilter _ (algebraicTyCon -> Just _) = True+destructFilter _ _ = False+
src/Ide/Plugin/Tactic/Machinery.hs view
@@ -1,7 +1,4 @@ {-# LANGUAGE ScopedTypeVariables #-}-{-# LANGUAGE DeriveFunctor         #-}-{-# LANGUAGE DeriveGeneric         #-}-{-# LANGUAGE DerivingStrategies    #-} {-# LANGUAGE DerivingVia           #-} {-# LANGUAGE FlexibleContexts      #-} {-# LANGUAGE FlexibleInstances     #-}@@ -9,9 +6,7 @@ {-# LANGUAGE MonoLocalBinds        #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE RecordWildCards       #-}-{-# LANGUAGE TypeSynonymInstances  #-} {-# LANGUAGE ViewPatterns          #-}-{-# LANGUAGE ViewPatterns          #-} {-# OPTIONS_GHC -fno-warn-orphans  #-}  module Ide.Plugin.Tactic.Machinery@@ -38,6 +33,7 @@ import qualified Data.Set as S import           Development.IDE.GHC.Compat import           Ide.Plugin.Tactic.Judgements+import           Ide.Plugin.Tactic.Simplify (simplify) import           Ide.Plugin.Tactic.Types import           OccName (HasOccName(occName)) import           Refinery.ProofState@@ -79,9 +75,11 @@                 $ (:) (jGoal jdg)                 $ fmap hi_type                 $ toList+                $ hyByName                 $ jHypothesis jdg         unused_topvals = M.keysSet                        $ M.filter (isTopLevel . hi_provenance)+                       $ hyByName                        $ jHypothesis jdg         tacticState =           defaultTacticState@@ -92,18 +90,21 @@           . flip runReader ctx           . unExtractM           $ runTacticT t jdg tacticState of-      (errs, []) -> Left $ take 50 $ errs+      (errs, []) -> Left $ take 50 errs       (_, fmap assoc23 -> solns) -> do         let sorted =               flip sortBy solns $ comparing $ \((_, ext), (jdg, holes)) ->                 Down $ scoreSolution ext jdg holes         case sorted of           (((tr, ext), _) : _) ->-            Right-              . RunTacticResults tr ext-              . reverse-              . fmap fst-              $ take 5 sorted+            Right $+              RunTacticResults+                { rtr_trace = tr+                , rtr_extract = simplify ext+                , rtr_other_solns = reverse . fmap fst $ take 5 sorted+                , rtr_jdg = jdg+                , rtr_ctx = ctx+                }           -- guaranteed to not be empty           _ -> Left [] @@ -174,7 +175,7 @@     , Penalize $ S.size ts_unused_top_vals     , Penalize $ S.size ts_intro_vals     , Reward   $ S.size ts_used_vals-    , Penalize $ ts_recursion_count+    , Penalize ts_recursion_count     , Penalize $ solutionSize ext     ) @@ -223,7 +224,7 @@ ------------------------------------------------------------------------------ -- | Get the class methods of a 'PredType', correctly dealing with -- instantiation of quantified class types.-methodHypothesis :: PredType -> Maybe [(OccName, HyInfo CType)]+methodHypothesis :: PredType -> Maybe [HyInfo CType] methodHypothesis ty = do   (tc, apps) <- splitTyConApp_maybe ty   cls <- tyConClass_maybe tc@@ -235,8 +236,7 @@               $ classSCTheta cls   pure $ mappend sc_methods $ methods <&> \method ->     let (_, _, ty) = tcSplitSigmaTy $ idType method-    in ( occName method-       , HyInfo (ClassMethodPrv $ Uniquely cls) $ CType $ substTy subst ty+    in ( HyInfo (occName method) (ClassMethodPrv $ Uniquely cls) $ CType $ substTy subst ty        )  @@ -251,4 +251,3 @@   case S.size $ vars S.\\ skolems of     0 -> m     _ -> throwError TooPolymorphic-
src/Ide/Plugin/Tactic/Naming.hs view
@@ -7,6 +7,8 @@ import           Data.Char import           Data.Map (Map) import qualified Data.Map as M+import           Data.Set (Set)+import qualified Data.Set as S import           Data.Traversable import           Name import           TcType@@ -22,16 +24,19 @@ mkTyName (tcSplitFunTys -> ([a@(isFunTy -> False)], b))   = "f" ++ mkTyName a ++ mkTyName b -- eg. mkTyName (a -> b -> C) = "f_C"-mkTyName (tcSplitFunTys -> ((_:_), b))+mkTyName (tcSplitFunTys -> (_:_, b))   = "f_" ++ mkTyName b -- eg. mkTyName (Either A B) = "eab" mkTyName (splitTyConApp_maybe -> Just (c, args))   = mkTyConName c ++ foldMap mkTyName args+-- eg. mkTyName (f a) = "fa"+mkTyName (tcSplitAppTys -> (t, args@(_:_)))+  = mkTyName t ++ foldMap mkTyName args -- eg. mkTyName a = "a" mkTyName (getTyVar_maybe -> Just tv)   = occNameString $ occName tv -- eg. mkTyName (forall x. y) = "y"-mkTyName (tcSplitSigmaTy -> ((_:_), _, t))+mkTyName (tcSplitSigmaTy -> (_:_, _, t))   = mkTyName t mkTyName _ = "x" @@ -61,12 +66,12 @@ ------------------------------------------------------------------------------ -- | Produce a unique, good name for a type. mkGoodName-    :: [OccName]  -- ^ Bindings in scope; used to ensure we don't shadow anything+    :: Set OccName  -- ^ Bindings in scope; used to ensure we don't shadow anything     -> Type       -- ^ The type to produce a name for     -> OccName mkGoodName in_scope t =   let tn = mkTyName t-   in mkVarOcc $ case elem (mkVarOcc tn) in_scope of+   in mkVarOcc $ case S.member (mkVarOcc tn) in_scope of         True -> tn ++ show (length in_scope)         False -> tn @@ -75,14 +80,14 @@ -- | Like 'mkGoodName' but creates several apart names. mkManyGoodNames   :: (Traversable t, Monad m)-  => M.Map OccName a+  => Set OccName   -> t Type   -> m (t OccName)-mkManyGoodNames hy args =-  flip evalStateT (getInScope hy) $ for args $ \at -> do+mkManyGoodNames in_scope args =+  flip evalStateT in_scope $ for args $ \at -> do     in_scope <- get     let n = mkGoodName in_scope at-    modify (n :)+    modify $ S.insert n     pure n  @@ -90,4 +95,3 @@ -- | Which names are in scope? getInScope :: Map OccName a -> [OccName] getInScope = M.keys-
+ src/Ide/Plugin/Tactic/Simplify.hs view
@@ -0,0 +1,108 @@+{-# LANGUAGE LambdaCase          #-}+{-# LANGUAGE OverloadedStrings   #-}+{-# LANGUAGE PatternSynonyms     #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE ViewPatterns        #-}++module Ide.Plugin.Tactic.Simplify+  ( simplify+  ) where++import Data.Generics (everywhere, mkT, GenericT)+import Data.List.Extra (unsnoc)+import Data.Monoid (Endo (..))+import Development.IDE.GHC.Compat+import GHC.SourceGen (var)+import GHC.SourceGen.Expr (lambda)+import Ide.Plugin.Tactic.CodeGen.Utils+import Ide.Plugin.Tactic.GHC (fromPatCompatPs, containsHsVar)+++------------------------------------------------------------------------------+-- | A pattern over the otherwise (extremely) messy AST for lambdas.+pattern Lambda :: [Pat GhcPs] -> HsExpr GhcPs -> HsExpr GhcPs+pattern Lambda pats body <-+  HsLam _+    (MG {mg_alts = L _ [L _+      (Match { m_pats = fmap fromPatCompatPs -> pats+             , m_grhss = GRHSs {grhssGRHSs = [L _ (+                 GRHS _ [] (L _ body))]}+             })]})+  where+    -- If there are no patterns to bind, just stick in the body+    Lambda [] body   = body+    Lambda pats body = lambda pats body+++------------------------------------------------------------------------------+-- | Simlify an expression.+simplify :: LHsExpr GhcPs -> LHsExpr GhcPs+simplify+  = head+  . drop 3   -- Do three passes; this should be good enough for the limited+             -- amount of gas we give to auto+  . iterate (everywhere $ foldEndo+    [ simplifyEtaReduce+    , simplifyRemoveParens+    , simplifyCompose+    ])+++------------------------------------------------------------------------------+-- | Like 'foldMap' but for endomorphisms.+foldEndo :: Foldable t => t (a -> a) -> a -> a+foldEndo = appEndo . foldMap Endo+++------------------------------------------------------------------------------+-- | Perform an eta reduction. For example, transforms @\x -> (f g) x@ into+-- @f g@.+simplifyEtaReduce :: GenericT+simplifyEtaReduce = mkT $ \case+  Lambda+      [VarPat _ (L _ pat)]+      (HsVar _ (L _ a)) | pat == a ->+    var "id"+  Lambda+      (unsnoc -> Just (pats, (VarPat _ (L _ pat))))+      (HsApp _ (L _ f) (L _ (HsVar _ (L _ a))))+      | pat == a+        -- We can only perform this simplifiation if @pat@ is otherwise unused.+      , not (containsHsVar pat f) ->+    Lambda pats f+  x -> x+++------------------------------------------------------------------------------+-- | Perform an eta-reducing function composition. For example, transforms+-- @\x -> f (g (h x))@ into @f . g . h@.+simplifyCompose :: GenericT+simplifyCompose = mkT $ \case+  Lambda+      (unsnoc -> Just (pats, (VarPat _ (L _ pat))))+      (unroll -> (fs@(_:_), (HsVar _ (L _ a))))+      | pat == a+        -- We can only perform this simplifiation if @pat@ is otherwise unused.+      , not (containsHsVar pat fs) ->+    Lambda pats (foldr1 (infixCall ".") fs)+  x -> x+++------------------------------------------------------------------------------+-- | Removes unnecessary parentheses on any token that doesn't need them.+simplifyRemoveParens :: GenericT+simplifyRemoveParens = mkT $ \case+  HsPar _ (L _ x) | isAtomicHsExpr x -> x+  (x :: HsExpr GhcPs) -> x+++------------------------------------------------------------------------------+-- | Unrolls a right-associative function application of the form+-- @HsApp f (HsApp g (HsApp h x))@ into @([f, g, h], x)@.+unroll :: HsExpr GhcPs -> ([HsExpr GhcPs], HsExpr GhcPs)+unroll (HsPar _ (L _ x)) = unroll x+unroll (HsApp _ (L _ f) (L _ a)) =+  let (fs, r) = unroll a+   in (f : fs, r)+unroll x = ([], x)+
src/Ide/Plugin/Tactic/Tactics.hs view
@@ -1,12 +1,7 @@ {-# LANGUAGE TupleSections #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE TypeApplications #-}-{-# LANGUAGE DeriveGeneric         #-} {-# LANGUAGE FlexibleContexts      #-} {-# LANGUAGE FlexibleInstances     #-}-{-# LANGUAGE LambdaCase            #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE TypeSynonymInstances  #-} {-# LANGUAGE ViewPatterns          #-}  module Ide.Plugin.Tactic.Tactics@@ -24,6 +19,7 @@ import           Data.List import qualified Data.Map as M import           Data.Maybe+import           Data.Set (Set) import qualified Data.Set as S import           DataCon import           Development.IDE.GHC.Compat@@ -47,7 +43,7 @@ ------------------------------------------------------------------------------ -- | Use something in the hypothesis to fill the hole. assumption :: TacticsM ()-assumption = attemptOn allNames assume+assumption = attemptOn (S.toList . allNames) assume   ------------------------------------------------------------------------------@@ -55,7 +51,7 @@ assume :: OccName -> TacticsM () assume name = rule $ \jdg -> do   let g  = jGoal jdg-  case M.lookup name $ jHypothesis jdg of+  case M.lookup name $ hyByName $ jHypothesis jdg of     Just (hi_type -> ty) -> do       unify ty $ jGoal jdg       for_ (M.lookup name $ jPatHypothesis jdg) markStructuralySmallerRecursion@@ -67,10 +63,10 @@ recursion :: TacticsM () recursion = requireConcreteHole $ tracing "recursion" $ do   defs <- getCurrentDefinitions-  attemptOn (const $ fmap fst defs) $ \name -> do+  attemptOn (const defs) $ \(name, ty) -> do     modify $ pushRecursionStack .  countRecursiveCall     ensure guardStructurallySmallerRecursion popRecursionStack $ do-      (localTactic (apply name) $ introducingRecursively defs)+      (localTactic (apply $ HyInfo name RecursivePrv ty) $ introducingRecursively defs)         <@> fmap (localTactic assumption . filterPosition name) [0..]  @@ -84,7 +80,7 @@   case tcSplitFunTys $ unCType g of     ([], _) -> throwError $ GoalMismatch "intros" g     (as, b) -> do-      vs <- mkManyGoodNames (jEntireHypothesis jdg) as+      vs <- mkManyGoodNames (hyNamesInScope $ jEntireHypothesis jdg) as       let top_hole = isTopHole ctx jdg           jdg' = introducingLambda top_hole (zip vs $ coerce as)                $ withNewGoal (CType b) jdg@@ -100,35 +96,32 @@  ------------------------------------------------------------------------------ -- | Case split, and leave holes in the matches.-destructAuto :: OccName -> TacticsM ()-destructAuto name = requireConcreteHole $ tracing "destruct(auto)" $ do+destructAuto :: HyInfo CType -> TacticsM ()+destructAuto hi = requireConcreteHole $ tracing "destruct(auto)" $ do   jdg <- goal-  case M.lookup name $ jHypothesis jdg of-    Nothing -> throwError $ NotInScope name-    Just hi ->-      let subtactic = rule $ destruct' (const subgoal) name-      in case isPatternMatch $ hi_provenance hi of-            True ->-              pruning subtactic $ \jdgs ->-                let getHyTypes = S.fromList . fmap hi_type . M.elems . jHypothesis-                    new_hy = foldMap getHyTypes jdgs-                    old_hy = getHyTypes jdg-                in case S.null $ new_hy S.\\ old_hy of-                      True  -> Just $ UnhelpfulDestruct name-                      False -> Nothing-            False -> subtactic+  let subtactic = rule $ destruct' (const subgoal) hi+  case isPatternMatch $ hi_provenance hi of+    True ->+      pruning subtactic $ \jdgs ->+        let getHyTypes = S.fromList . fmap hi_type . unHypothesis . jHypothesis+            new_hy = foldMap getHyTypes jdgs+            old_hy = getHyTypes jdg+        in case S.null $ new_hy S.\\ old_hy of+              True  -> Just $ UnhelpfulDestruct $ hi_name hi+              False -> Nothing+    False -> subtactic   ------------------------------------------------------------------------------ -- | Case split, and leave holes in the matches.-destruct :: OccName -> TacticsM ()-destruct name = requireConcreteHole $ tracing "destruct(user)" $-  rule $ destruct' (const subgoal) name+destruct :: HyInfo CType -> TacticsM ()+destruct hi = requireConcreteHole $ tracing "destruct(user)" $+  rule $ destruct' (const subgoal) hi   ------------------------------------------------------------------------------ -- | Case split, using the same data constructor in the matches.-homo :: OccName -> TacticsM ()+homo :: HyInfo CType -> TacticsM () homo = requireConcreteHole . tracing "homo" . rule . destruct' (\dc jdg ->   buildDataCon jdg dc $ snd $ splitAppTys $ unCType $ jGoal jdg) @@ -152,32 +145,30 @@         $ jGoal jdg  -apply :: OccName -> TacticsM ()-apply func = requireConcreteHole $ tracing ("apply' " <> show func) $ do+apply :: HyInfo CType -> TacticsM ()+apply hi = requireConcreteHole $ tracing ("apply' " <> show (hi_name hi)) $ do   jdg <- goal   let hy = jHypothesis jdg       g  = jGoal jdg-  case M.lookup func hy of-    Just (hi_type -> CType ty) -> do-      ty' <- freshTyvars ty-      let (_, _, args, ret) = tacticsSplitFunTy ty'-      requireNewHoles $ rule $ \jdg -> do-        unify g (CType ret)-        useOccName jdg func-        (tr, sgs)-            <- fmap unzipTrace-            $ traverse ( newSubgoal-                        . blacklistingDestruct-                        . flip withNewGoal jdg-                        . CType-                        ) args-        pure-          . (tr, )-          . noLoc-          . foldl' (@@) (var' func)-          $ fmap unLoc sgs-    Nothing -> do-      throwError $ GoalMismatch "apply" g+      ty = unCType $ hi_type hi+      func = hi_name hi+  ty' <- freshTyvars ty+  let (_, _, args, ret) = tacticsSplitFunTy ty'+  requireNewHoles $ rule $ \jdg -> do+    unify g (CType ret)+    useOccName jdg func+    (tr, sgs)+        <- fmap unzipTrace+        $ traverse ( newSubgoal+                    . blacklistingDestruct+                    . flip withNewGoal jdg+                    . CType+                    ) args+    pure+      . (tr, )+      . noLoc+      . foldl' (@@) (var' func)+      $ fmap unLoc sgs   ------------------------------------------------------------------------------@@ -272,16 +263,19 @@     , recursion     ] -overFunctions :: (OccName -> TacticsM ()) -> TacticsM ()+overFunctions :: (HyInfo CType -> TacticsM ()) -> TacticsM () overFunctions =-  attemptOn $ M.keys . M.filter (isFunction . unCType . hi_type) . jHypothesis+  attemptOn $ filter (isFunction . unCType . hi_type)+           . unHypothesis+           . jHypothesis -overAlgebraicTerms :: (OccName -> TacticsM ()) -> TacticsM ()+overAlgebraicTerms :: (HyInfo CType -> TacticsM ()) -> TacticsM () overAlgebraicTerms =-  attemptOn $-    M.keys . M.filter (isJust . algebraicTyCon . unCType . hi_type) . jHypothesis+  attemptOn $ filter (isJust . algebraicTyCon . unCType . hi_type)+            . unHypothesis+            . jHypothesis  -allNames :: Judgement -> [OccName]-allNames = M.keys . jHypothesis+allNames :: Judgement -> Set OccName+allNames = hyNamesInScope . jHypothesis 
src/Ide/Plugin/Tactic/TestTypes.hs view
@@ -3,6 +3,8 @@ module Ide.Plugin.Tactic.TestTypes where  import qualified Data.Text as T+import Data.Aeson+import Ide.Plugin.Tactic.FeatureSet  ------------------------------------------------------------------------------ -- | The list of tactics exposed to the outside world. These are attached to@@ -25,3 +27,24 @@ tacticTitle Homomorphism var = "Homomorphic case split on " <> var tacticTitle DestructLambdaCase _ = "Lambda case split" tacticTitle HomomorphismLambdaCase _ = "Homomorphic lambda case split"+++------------------------------------------------------------------------------+-- | Plugin configuration for tactics+newtype Config = Config+  { cfg_feature_set :: FeatureSet+  }++emptyConfig :: Config+emptyConfig = Config defaultFeatures++instance ToJSON Config where+  toJSON (Config features) = object+    [ "features" .= prettyFeatureSet features+    ]++instance FromJSON Config where+  parseJSON = withObject "Config" $ \obj -> do+    features <- parseFeatureSet <$> obj .: "features"+    pure $ Config features+
src/Ide/Plugin/Tactic/Types.hs view
@@ -1,14 +1,12 @@-{-# LANGUAGE DerivingVia #-}-{-# LANGUAGE FlexibleContexts #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE TypeApplications #-} {-# LANGUAGE DeriveFunctor              #-} {-# LANGUAGE DeriveGeneric              #-}-{-# LANGUAGE DerivingStrategies         #-}+{-# LANGUAGE DerivingVia                #-}+{-# LANGUAGE FlexibleContexts           #-} {-# LANGUAGE FlexibleInstances          #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-} {-# LANGUAGE MultiParamTypeClasses      #-}-{-# LANGUAGE TypeSynonymInstances       #-}+{-# LANGUAGE OverloadedStrings          #-}+{-# LANGUAGE TypeApplications           #-} {-# OPTIONS_GHC -fno-warn-orphans       #-}  module Ide.Plugin.Tactic.Types@@ -22,13 +20,12 @@   , Range   ) where -import Control.Lens hiding (Context)+import Control.Lens hiding (Context, (.=)) import Control.Monad.Reader import Control.Monad.State import Data.Coerce import Data.Function import Data.Generics.Product (field)-import Data.Map (Map) import Data.Set (Set) import Data.Tree import Development.IDE.GHC.Compat hiding (Node)@@ -36,6 +33,7 @@ import Development.IDE.Types.Location import GHC.Generics import Ide.Plugin.Tactic.Debug+import Ide.Plugin.Tactic.FeatureSet (FeatureSet) import OccName import Refinery.Tactic import System.IO.Unsafe (unsafePerformIO)@@ -72,12 +70,18 @@ instance Show Class where   show  = unsafeRender +instance Show (HsExpr GhcPs) where+  show  = unsafeRender +instance Show (Pat GhcPs) where+  show  = unsafeRender++ ------------------------------------------------------------------------------ data TacticState = TacticState     { ts_skolems   :: !(Set TyVar)       -- ^ The known skolems.-    , ts_unifier   :: !(TCvSubst)+    , ts_unifier   :: !TCvSubst       -- ^ The current substitution of univars.     , ts_used_vals :: !(Set OccName)       -- ^ Set of values used by tactics.@@ -217,10 +221,18 @@   compare = nonDetCmpUnique `on` getUnique . getViaUnique  +newtype Hypothesis a = Hypothesis+  { unHypothesis :: [HyInfo a]+  }+  deriving stock (Functor, Eq, Show, Generic, Ord)+  deriving newtype (Semigroup, Monoid)++ ------------------------------------------------------------------------------ -- | The provenance and type of a hypothesis term. data HyInfo a = HyInfo-  { hi_provenance :: Provenance+  { hi_name       :: OccName+  , hi_provenance :: Provenance   , hi_type       :: a   }   deriving stock (Functor, Eq, Show, Generic, Ord)@@ -229,17 +241,17 @@ ------------------------------------------------------------------------------ -- | Map a function over the provenance. overProvenance :: (Provenance -> Provenance) -> HyInfo a -> HyInfo a-overProvenance f (HyInfo prv ty) = HyInfo (f prv) ty+overProvenance f (HyInfo name prv ty) = HyInfo name (f prv) ty   ------------------------------------------------------------------------------ -- | The current bindings and goal for a hole to be filled by refinery. data Judgement' a = Judgement-  { _jHypothesis :: !(Map OccName (HyInfo a))-  , _jBlacklistDestruct :: !(Bool)-  , _jWhitelistSplit :: !(Bool)+  { _jHypothesis :: !(Hypothesis a)+  , _jBlacklistDestruct :: !Bool+  , _jWhitelistSplit :: !Bool   , _jIsTopHole    :: !Bool-  , _jGoal         :: !(a)+  , _jGoal         :: !a   }   deriving stock (Eq, Generic, Functor, Show) @@ -325,14 +337,15 @@     -- ^ The functions currently being defined   , ctxModuleFuncs :: [(OccName, CType)]     -- ^ Everything defined in the current module+  , ctxFeatureSet :: FeatureSet   }-  deriving stock (Eq, Ord)+  deriving stock (Eq, Ord, Show)   ------------------------------------------------------------------------------ -- | An empty context emptyContext :: Context-emptyContext  = Context mempty mempty+emptyContext  = Context mempty mempty mempty   newtype Rose a = Rose (Tree a)@@ -363,5 +376,14 @@   { rtr_trace       :: Trace   , rtr_extract     :: LHsExpr GhcPs   , rtr_other_solns :: [(Trace, LHsExpr GhcPs)]+  , rtr_jdg         :: Judgement+  , rtr_ctx         :: Context   } deriving Show+++data AgdaMatch = AgdaMatch+  { amPats :: [Pat GhcPs]+  , amBody :: HsExpr GhcPs+  }+  deriving (Show) 
− src/Ide/TreeTransform.hs
@@ -1,122 +0,0 @@-{-# LANGUAGE LambdaCase          #-}-{-# LANGUAGE NamedFieldPuns      #-}-{-# LANGUAGE OverloadedStrings   #-}-{-# LANGUAGE RecordWildCards     #-}-{-# LANGUAGE ScopedTypeVariables #-}--module Ide.TreeTransform-  ( Graft, graft, transform, useAnnotatedSource-  ) where--import           BasicTypes (appPrec)-import           Control.Monad-import           Control.Monad.Trans.Class-import qualified Data.Text as T-import           Development.IDE.Core.RuleTypes-import           Development.IDE.Core.Rules-import           Development.IDE.Core.Shake-import           Development.IDE.GHC.Compat  hiding (parseExpr)-import           Development.IDE.Types.Location-import           Generics.SYB-import           Ide.PluginUtils-import           Language.Haskell.GHC.ExactPrint-import           Language.Haskell.GHC.ExactPrint.Parsers-import           Language.Haskell.LSP.Types-import           Language.Haskell.LSP.Types.Capabilities (ClientCapabilities)-import           Outputable-import           Retrie.ExactPrint hiding (parseExpr)------------------------------------------------------------------------------------ | Get the latest version of the annotated parse source.-useAnnotatedSource-    :: String-    -> IdeState-    -> NormalizedFilePath-    -> IO (Maybe (Annotated ParsedSource))-useAnnotatedSource herald state nfp = do-  pm <- runAction herald state $ use GetParsedModule nfp-  pure $ fmap fixAnns pm------------------------------------------------------------------------------------ | A transformation for grafting source trees together. Use the semigroup--- instance to combine 'Graft's, and run them via 'transform'.-newtype Graft a = Graft-  { runGraft :: DynFlags -> a -> TransformT (Either String) a-  }--instance Semigroup (Graft a) where-  Graft a <> Graft b = Graft $ \dflags -> a dflags >=> b dflags--instance Monoid (Graft a) where-  mempty = Graft $ const pure------------------------------------------------------------------------------------ | Convert a 'Graft' into a 'WorkspaceEdit'.-transform-    :: DynFlags-    -> ClientCapabilities-    -> Uri-    -> Graft ParsedSource-    -> Annotated ParsedSource-    -> Either String WorkspaceEdit-transform dflags ccs uri f a = do-  let src = printA a-  a' <- transformA a $ runGraft f dflags-  let res = printA a'-  pure $ diffText ccs (uri, T.pack src) (T.pack res) IncludeDeletions------------------------------------------------------------------------------------ | Construct a 'Graft', replacing the node at the given 'SrcSpan' with the--- given 'LHSExpr'. The node at that position must already be a 'LHsExpr', or--- this is a no-op.-graft-    :: forall a-     . Data a-    => SrcSpan-    -> LHsExpr GhcPs-    -> Graft a-graft dst val = Graft $ \dflags a -> do-  (anns, val') <- annotate dflags $ parenthesize val-  modifyAnnsT $ mappend anns-  pure $ everywhere'-    ( mkT $-        \case-          (L src _ :: LHsExpr GhcPs) | src == dst -> val'-          l -> l-    ) a------------------------------------------------------------------------------------ | Dark magic I stole from retrie. No idea what it does.-fixAnns :: ParsedModule -> Annotated ParsedSource-fixAnns ParsedModule {..} =-  let ranns = relativiseApiAnns pm_parsed_source pm_annotations-   in unsafeMkA pm_parsed_source ranns 0------------------------------------------------------------------------------------ | Given an 'LHSExpr', compute its exactprint annotations.-annotate :: DynFlags -> LHsExpr GhcPs -> TransformT (Either String) (Anns, LHsExpr GhcPs)-annotate dflags expr = do-  uniq <- show <$> uniqueSrcSpanT-  let rendered = render dflags expr-  (anns, expr') <- lift $ either (Left . show) Right $ parseExpr dflags uniq rendered-  let anns' = setPrecedingLines expr' 0 1 anns-  pure (anns', expr')------------------------------------------------------------------------------------ | Print out something 'Outputable'.-render :: Outputable a => DynFlags -> a -> String-render dflags = showSDoc dflags . ppr------------------------------------------------------------------------------------ | Put parentheses around an expression if required.-parenthesize :: LHsExpr GhcPs -> LHsExpr GhcPs-parenthesize = parenthesizeHsExpr appPrec-
test/AutoTupleSpec.hs view
@@ -2,23 +2,17 @@  module AutoTupleSpec where -import           Data.Either (isRight)-import qualified Data.Map as M-import           Ide.Plugin.Tactic.Debug-import           Ide.Plugin.Tactic.Judgements (mkFirstJudgement)-import           Ide.Plugin.Tactic.Machinery-import           Ide.Plugin.Tactic.Tactics (auto')-import           Ide.Plugin.Tactic.Types-import           OccName (mkVarOcc)-import           Test.Hspec-import           Test.QuickCheck-import           Type (mkTyVarTy)-import           TysPrim (alphaTyVars)-import           TysWiredIn (mkBoxedTupleTy)---instance Show Type where-  show = unsafeRender+import Data.Either (isRight)+import Ide.Plugin.Tactic.Judgements (mkFirstJudgement)+import Ide.Plugin.Tactic.Machinery+import Ide.Plugin.Tactic.Tactics (auto')+import Ide.Plugin.Tactic.Types+import OccName (mkVarOcc)+import Test.Hspec+import Test.QuickCheck+import Type (mkTyVarTy)+import TysPrim (alphaTyVars)+import TysWiredIn (mkBoxedTupleTy)   spec :: Spec@@ -42,7 +36,7 @@           runTactic             emptyContext             (mkFirstJudgement-              (M.singleton (mkVarOcc "x") $ HyInfo UserPrv $ CType in_type)+              (Hypothesis $ pure $ HyInfo (mkVarOcc "x") UserPrv $ CType in_type)               True               out_type)             (auto' $ n * 2) `shouldSatisfy` isRight
+ test/GoldenSpec.hs view
@@ -0,0 +1,224 @@+{-# LANGUAGE DataKinds             #-}+{-# LANGUAGE DuplicateRecordFields #-}+{-# LANGUAGE OverloadedStrings     #-}+{-# LANGUAGE ScopedTypeVariables   #-}+{-# LANGUAGE TypeOperators         #-}+{-# LANGUAGE ViewPatterns          #-}++module GoldenSpec where++import           Control.Applicative.Combinators ( skipManyTill )+import           Control.Lens hiding ((<.>), failing)+import           Control.Monad (unless)+import           Control.Monad.IO.Class+import           Data.Aeson+import           Data.Default (Default(def))+import           Data.Foldable+import qualified Data.Map as M+import           Data.Maybe+import           Data.Text (Text)+import qualified Data.Text.IO as T+import qualified Ide.Plugin.Config as Plugin+import           Ide.Plugin.Tactic.FeatureSet (FeatureSet, allFeatures)+import           Ide.Plugin.Tactic.TestTypes+import           Language.LSP.Test+import           Language.LSP.Types+import           Language.LSP.Types.Lens hiding (id, capabilities, message, executeCommand, applyEdit, rename, line, title, name, actions)+import           System.Directory (doesFileExist)+import           System.FilePath+import           Test.Hspec+++spec :: Spec+spec = do+  describe "code action availability" $ do+    mkTest+      "Produces intros code action"+      "T1.hs" 2 14+      [ (id, Intros, "")+      ]+    mkTest+      "Produces destruct and homomorphism code actions"+      "T2.hs" 2 21+      [ (id, Destruct, "eab")+      , (id, Homomorphism, "eab")+      ]+    mkTest+      "Won't suggest homomorphism on the wrong type"+      "T2.hs" 8 8+      [ (not, Homomorphism, "global")+      ]+    mkTest+      "Won't suggest intros on the wrong type"+      "T2.hs" 8 8+      [ (not, Intros, "")+      ]+    mkTest+      "Produces (homomorphic) lambdacase code actions"+      "T3.hs" 4 24+      [ (id, HomomorphismLambdaCase, "")+      , (id, DestructLambdaCase, "")+      ]+    mkTest+      "Produces lambdacase code actions"+      "T3.hs" 7 13+      [ (id, DestructLambdaCase, "")+      ]+    mkTest+      "Doesn't suggest lambdacase without -XLambdaCase"+      "T2.hs" 11 25+      [ (not, DestructLambdaCase, "")+      ]++  describe "golden tests" $ do+    let goldenTest = mkGoldenTest allFeatures+        autoTest = mkGoldenTest allFeatures Auto ""++    goldenTest Intros "" "GoldenIntros.hs" 2 8+    autoTest "GoldenEitherAuto.hs"        2 11+    autoTest "GoldenJoinCont.hs"          4 12+    autoTest "GoldenIdentityFunctor.hs"   3 11+    autoTest "GoldenIdTypeFam.hs"         7 11+    autoTest "GoldenEitherHomomorphic.hs" 2 15+    autoTest "GoldenNote.hs"              2 8+    autoTest "GoldenPureList.hs"          2 12+    autoTest "GoldenListFmap.hs"          2 12+    autoTest "GoldenFromMaybe.hs"         2 13+    autoTest "GoldenFoldr.hs"             2 10+    autoTest "GoldenSwap.hs"              2 8+    autoTest "GoldenFmapTree.hs"          4 11+    goldenTest Destruct "gadt"+             "GoldenGADTDestruct.hs"      7 17+    goldenTest Destruct "gadt"+             "GoldenGADTDestructCoercion.hs" 8 17+    autoTest "GoldenGADTAuto.hs"    7 13+    autoTest "GoldenSwapMany.hs"    2 12+    autoTest "GoldenBigTuple.hs"    4 12+    autoTest "GoldenShow.hs"        2 10+    autoTest "GoldenShowCompose.hs" 2 15+    autoTest "GoldenShowMapChar.hs" 2 8+    autoTest "GoldenSuperclass.hs"  7 8+    failing "flaky in CI" $+      autoTest "GoldenApplicativeThen.hs" 2 11+    autoTest "GoldenSafeHead.hs"  2 12+    failing "not enough auto gas" $+      autoTest "GoldenFish.hs" 5 18+    autoTest "GoldenArbitrary.hs" 25 13+    autoTest "FmapBoth.hs"        2 12+    autoTest "RecordCon.hs"       7  8+    autoTest "FmapJoin.hs"        2 14+    autoTest "Fgmap.hs"           2 9+    autoTest "FmapJoinInLet.hs"   4 19+    goldenTest Destruct "a"+      "SplitPattern.hs"  7 25+++------------------------------------------------------------------------------+-- | Get a range at the given line and column corresponding to having nothing+-- selected.+--+-- NB: These coordinates are in "file space", ie, 1-indexed.+pointRange :: Int -> Int -> Range+pointRange+  (subtract 1 -> line)+  (subtract 1 -> col) =+    Range (Position line col) (Position line $ col + 1)+++------------------------------------------------------------------------------+-- | Get the title of a code action.+codeActionTitle :: (Command |? CodeAction) -> Maybe Text+codeActionTitle InL{} = Nothing+codeActionTitle (InR(CodeAction title _ _ _ _ _ _)) = Just title+++------------------------------------------------------------------------------+-- | Make a tactic unit test.+mkTest+    :: Foldable t+    => String  -- ^ The test name+    -> FilePath  -- ^ The file to load+    -> Int  -- ^ Cursor line+    -> Int  -- ^ Cursor columnn+    -> t ( Bool -> Bool   -- Use 'not' for actions that shouldnt be present+         , TacticCommand  -- An expected command ...+         , Text           -- ... for this variable+         ) -- ^ A collection of (un)expected code actions.+    -> SpecWith (Arg Bool)+mkTest name fp line col ts = it name $ do+  runSession testCommand fullCaps tacticPath $ do+    doc <- openDoc fp "haskell"+    _ <- waitForDiagnostics+    actions <- getCodeActions doc $ pointRange line col+    let titles = mapMaybe codeActionTitle actions+    for_ ts $ \(f, tc, var) -> do+      let title = tacticTitle tc var+      liftIO $+        (title `elem` titles) `shouldSatisfy` f+++setFeatureSet :: FeatureSet -> Session ()+setFeatureSet features = do+  let unObject (Object obj) = obj+      unObject _ = undefined+      def_config = def :: Plugin.Config+      config =+        def_config+          { Plugin.plugins = M.fromList [("tactics",+              def { Plugin.plcConfig = unObject $ toJSON $+                emptyConfig { cfg_feature_set = features }}+          )] <> Plugin.plugins def_config }++  sendNotification SWorkspaceDidChangeConfiguration $+    DidChangeConfigurationParams $+      toJSON config+++mkGoldenTest+    :: FeatureSet+    -> TacticCommand+    -> Text+    -> FilePath+    -> Int+    -> Int+    -> SpecWith ()+mkGoldenTest features tc occ input line col =+  it (input <> " (golden)") $ do+    runSession testCommand fullCaps tacticPath $ do+      setFeatureSet features+      doc <- openDoc input "haskell"+      _ <- waitForDiagnostics+      actions <- getCodeActions doc $ pointRange line col+      Just (InR CodeAction {_command = Just c})+        <- pure $ find ((== Just (tacticTitle tc occ)) . codeActionTitle) actions+      executeCommand c+      _resp <- skipManyTill anyMessage (message SWorkspaceApplyEdit)+      edited <- documentContents doc+      let expected_name = tacticPath </> input <.> "expected"+      -- Write golden tests if they don't already exist+      liftIO $ (doesFileExist expected_name >>=) $ flip unless $ do+        T.writeFile expected_name edited+      expected <- liftIO $ T.readFile expected_name+      liftIO $ edited `shouldBe` expected+++------------------------------------------------------------------------------+-- | Don't run a test.+failing :: Applicative m => String -> b -> m ()+failing _ _ = pure ()+++tacticPath :: FilePath+tacticPath = "test/golden"+++testCommand :: String+testCommand = "test-server"+++executeCommandWithResp :: Command -> Session (ResponseMessage 'WorkspaceExecuteCommand)+executeCommandWithResp cmd = do+  let args = decode $ encode $ fromJust $ cmd ^. arguments+      execParams = ExecuteCommandParams Nothing (cmd ^. command) args+  request SWorkspaceExecuteCommand execParams+
+ test/Server.hs view
@@ -0,0 +1,19 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ViewPatterns      #-}++module Main(main) where++import           Data.Default+import           Development.IDE.Main+import qualified Development.IDE.Plugin.HLS.GhcIde as Ghcide+import           Ide.Plugin.Tactic as T+import           Ide.PluginUtils++main :: IO ()+main = defaultMain def+  { argsHlsPlugins = pluginDescToIdePlugins $+    [ T.descriptor "tactic"+    ] <>+    Ghcide.descriptors+  }+