diff --git a/cabal.project b/cabal.project index 51d8cc1aba2..67816439547 100644 --- a/cabal.project +++ b/cabal.project @@ -4,12 +4,14 @@ packages: ./shake-bench ./ghcide ./hls-plugin-api + ./hls-exactprint-utils ./plugins/tactics ./plugins/hls-class-plugin ./plugins/hls-eval-plugin ./plugins/hls-explicit-imports-plugin ./plugins/hls-hlint-plugin ./plugins/hls-retrie-plugin + ./plugins/hls-splice-plugin tests: true diff --git a/exe/Plugins.hs b/exe/Plugins.hs index 1bd2336e9cc..ed813730753 100644 --- a/exe/Plugins.hs +++ b/exe/Plugins.hs @@ -45,6 +45,10 @@ import Ide.Plugin.ModuleName as ModuleName import Ide.Plugin.Pragmas as Pragmas #endif +#if splice +import Ide.Plugin.Splice as Splice +#endif + -- formatters #if floskell @@ -120,6 +124,9 @@ idePlugins includeExamples = pluginDescToIdePlugins allPlugins #endif #if hlint , Hlint.descriptor "hlint" +#endif +#if splice + , Splice.descriptor "splice" #endif ] examplePlugins = diff --git a/ghcide/ghcide.cabal b/ghcide/ghcide.cabal index e963e4e6fd7..c00ec747941 100644 --- a/ghcide/ghcide.cabal +++ b/ghcide/ghcide.cabal @@ -46,6 +46,7 @@ library data-default, deepseq, directory, + dlist, extra, fuzzy, filepath, diff --git a/ghcide/src/Development/IDE/Core/Compile.hs b/ghcide/src/Development/IDE/Core/Compile.hs index 86401c2c9f5..feaa183ab09 100644 --- a/ghcide/src/Development/IDE/Core/Compile.hs +++ b/ghcide/src/Development/IDE/Core/Compile.hs @@ -71,8 +71,11 @@ import StringBuffer as SB import TcRnMonad import TcIface (typecheckIface) import TidyPgm +import Hooks +import TcSplice import Control.Exception.Safe +import Control.Lens hiding (List) import Control.Monad.Extra import Control.Monad.Except import Control.Monad.Trans.Except @@ -85,10 +88,12 @@ import Data.Maybe import qualified Data.Map.Strict as Map import System.FilePath import System.Directory -import System.IO.Extra +import System.IO.Extra ( fixIO, newTempFileWithin ) import Control.Exception (evaluate) import TcEnv (tcLookup) +import qualified Data.DList as DL import Data.Time (UTCTime, getCurrentTime) +import Bag import Linker (unload) import qualified GHC.LanguageExtensions as LangExt import PrelNames @@ -144,21 +149,61 @@ typecheckModule (IdeDefer defer) hsc keep_lbls pm = do where demoteIfDefer = if defer then demoteTypeErrorsToWarnings else id +-- | Add a Hook to the DynFlags which captures and returns the +-- typechecked splices before they are run. This information +-- is used for hover. +captureSplices :: DynFlags -> (DynFlags -> IO a) -> IO (a, Splices) +captureSplices dflags k = do + splice_ref <- newIORef mempty + res <- k (dflags { hooks = addSpliceHook splice_ref (hooks dflags)}) + splices <- readIORef splice_ref + return (res, splices) + where + addSpliceHook :: IORef Splices -> Hooks -> Hooks + addSpliceHook var h = h { runMetaHook = Just (splice_hook (runMetaHook h) var) } + + splice_hook :: Maybe (MetaHook TcM) -> IORef Splices -> MetaHook TcM + splice_hook (fromMaybe defaultRunMeta -> hook) var metaReq e = case metaReq of + (MetaE f) -> do + expr' <- metaRequestE hook e + liftIO $ modifyIORef' var $ exprSplicesL %~ ((e, expr') :) + pure $ f expr' + (MetaP f) -> do + pat' <- metaRequestP hook e + liftIO $ modifyIORef' var $ patSplicesL %~ ((e, pat') :) + pure $ f pat' + (MetaT f) -> do + type' <- metaRequestT hook e + liftIO $ modifyIORef' var $ typeSplicesL %~ ((e, type') :) + pure $ f type' + (MetaD f) -> do + decl' <- metaRequestD hook e + liftIO $ modifyIORef' var $ declSplicesL %~ ((e, decl') :) + pure $ f decl' + (MetaAW f) -> do + aw' <- metaRequestAW hook e + liftIO $ modifyIORef' var $ awSplicesL %~ ((e, aw') :) + pure $ f aw' + + tcRnModule :: HscEnv -> [Linkable] -> ParsedModule -> IO TcModuleResult tcRnModule hsc_env keep_lbls pmod = do let ms = pm_mod_summary pmod hsc_env_tmp = hsc_env { hsc_dflags = ms_hspp_opts ms } unload hsc_env_tmp keep_lbls - (tc_gbl_env, mrn_info) <- - hscTypecheckRename hsc_env_tmp ms $ - HsParsedModule { hpm_module = parsedSource pmod, - hpm_src_files = pm_extra_src_files pmod, - hpm_annotations = pm_annotations pmod } + + ((tc_gbl_env, mrn_info), splices) + <- liftIO $ captureSplices (ms_hspp_opts ms) $ \dflags -> + do let hsc_env_tmp = hsc_env { hsc_dflags = dflags } + hscTypecheckRename hsc_env_tmp ms $ + HsParsedModule { hpm_module = parsedSource pmod, + hpm_src_files = pm_extra_src_files pmod, + hpm_annotations = pm_annotations pmod } let rn_info = case mrn_info of Just x -> x Nothing -> error "no renamed info tcRnModule" - pure (TcModuleResult pmod rn_info tc_gbl_env False) + pure (TcModuleResult pmod rn_info tc_gbl_env splices False) mkHiFileResultNoCompile :: HscEnv -> TcModuleResult -> IO HiFileResult mkHiFileResultNoCompile session tcm = do @@ -385,11 +430,26 @@ atomicFileWrite targetPath write = do generateHieAsts :: HscEnv -> TcModuleResult -> IO ([FileDiagnostic], Maybe (HieASTs Type)) generateHieAsts hscEnv tcm = - handleGenerationErrors' dflags "extended interface generation" $ runHsc hscEnv $ - Just <$> GHC.enrichHie (tcg_binds $ tmrTypechecked tcm) (tmrRenamed tcm) + handleGenerationErrors' dflags "extended interface generation" $ runHsc hscEnv $ do + -- These varBinds use unitDataConId but it could be anything as the id name is not used + -- during the hie file generation process. It's a workaround for the fact that the hie modules + -- don't export an interface which allows for additional information to be added to hie files. + let fake_splice_binds = listToBag (map (mkVarBind unitDataConId) (spliceExpresions $ tmrTopLevelSplices tcm)) + real_binds = tcg_binds $ tmrTypechecked tcm + Just <$> GHC.enrichHie (fake_splice_binds `unionBags` real_binds) (tmrRenamed tcm) where dflags = hsc_dflags hscEnv +spliceExpresions :: Splices -> [LHsExpr GhcTc] +spliceExpresions Splices{..} = + DL.toList $ mconcat + [ DL.fromList $ map fst exprSplices + , DL.fromList $ map fst patSplices + , DL.fromList $ map fst typeSplices + , DL.fromList $ map fst declSplices + , DL.fromList $ map fst awSplices + ] + writeHieFile :: HscEnv -> ModSummary -> [GHC.AvailInfo] -> HieASTs Type -> BS.ByteString -> IO [FileDiagnostic] writeHieFile hscEnv mod_summary exports ast source = handleGenerationErrors dflags "extended interface write/compression" $ do diff --git a/ghcide/src/Development/IDE/Core/RuleTypes.hs b/ghcide/src/Development/IDE/Core/RuleTypes.hs index 86bf2a75c97..39f61b5fed1 100644 --- a/ghcide/src/Development/IDE/Core/RuleTypes.hs +++ b/ghcide/src/Development/IDE/Core/RuleTypes.hs @@ -3,6 +3,7 @@ {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE PatternSynonyms #-} +{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE DerivingStrategies #-} @@ -14,6 +15,7 @@ module Development.IDE.Core.RuleTypes( ) where import Control.DeepSeq +import Control.Lens import Data.Aeson.Types (Value) import Data.Binary import Development.IDE.Import.DependencyInformation @@ -40,6 +42,7 @@ import qualified Data.ByteString.Char8 as BS import Development.IDE.Types.Options (IdeGhcSession) import Data.Text (Text) import Data.Int (Int64) +import GHC.Serialized (Serialized) data LinkableType = ObjectLinkable | BCOLinkable deriving (Eq,Ord,Show) @@ -90,13 +93,42 @@ newtype ImportMap = ImportMap } deriving stock Show deriving newtype NFData +data Splices = Splices + { exprSplices :: [(LHsExpr GhcTc, LHsExpr GhcPs)] + , patSplices :: [(LHsExpr GhcTc, LPat GhcPs)] + , typeSplices :: [(LHsExpr GhcTc, LHsType GhcPs)] + , declSplices :: [(LHsExpr GhcTc, [LHsDecl GhcPs])] + , awSplices :: [(LHsExpr GhcTc, Serialized)] + } + +instance Semigroup Splices where + Splices e p t d aw <> Splices e' p' t' d' aw' = + Splices + (e <> e') + (p <> p') + (t <> t') + (d <> d') + (aw <> aw') + +instance Monoid Splices where + mempty = Splices mempty mempty mempty mempty mempty + +instance NFData Splices where + rnf Splices {..} = + liftRnf rwhnf exprSplices `seq` + liftRnf rwhnf patSplices `seq` + liftRnf rwhnf typeSplices `seq` liftRnf rwhnf declSplices `seq` () + -- | Contains the typechecked module and the OrigNameCache entry for -- that module. data TcModuleResult = TcModuleResult { tmrParsed :: ParsedModule , tmrRenamed :: RenamedSource , tmrTypechecked :: TcGblEnv - , tmrDeferedError :: !Bool -- ^ Did we defer any type errors for this module? + , tmrTopLevelSplices :: Splices + -- ^ Typechecked splice information + , tmrDeferedError :: !Bool + -- ^ Did we defer any type errors for this module? } instance Show TcModuleResult where show = show . pm_mod_summary . tmrParsed @@ -398,3 +430,7 @@ data GhcSessionIO = GhcSessionIO deriving (Eq, Show, Typeable, Generic) instance Hashable GhcSessionIO instance NFData GhcSessionIO instance Binary GhcSessionIO + +makeLensesWith + (lensRules & lensField .~ mappingNamer (pure . (++ "L"))) + ''Splices diff --git a/ghcide/src/Development/IDE/GHC/Orphans.hs b/ghcide/src/Development/IDE/GHC/Orphans.hs index 135bbb211f2..9155ca2439d 100644 --- a/ghcide/src/Development/IDE/GHC/Orphans.hs +++ b/ghcide/src/Development/IDE/GHC/Orphans.hs @@ -12,12 +12,15 @@ module Development.IDE.GHC.Orphans() where import Bag import Control.DeepSeq +import Data.Aeson import Data.Hashable import Development.IDE.GHC.Compat import Development.IDE.GHC.Util import GHC () import GhcPlugins import qualified StringBuffer as SB +import Data.Text (Text) +import Data.String (IsString(fromString)) -- Orphan instances for types from the GHC API. @@ -94,6 +97,37 @@ instance NFData a => NFData (IdentifierDetails a) where instance NFData RealSrcSpan where rnf = rwhnf +srcSpanFileTag, srcSpanStartLineTag, srcSpanStartColTag, + srcSpanEndLineTag, srcSpanEndColTag :: Text +srcSpanFileTag = "srcSpanFile" +srcSpanStartLineTag = "srcSpanStartLine" +srcSpanStartColTag = "srcSpanStartCol" +srcSpanEndLineTag = "srcSpanEndLine" +srcSpanEndColTag = "srcSpanEndCol" + +instance ToJSON RealSrcSpan where + toJSON spn = + object + [ srcSpanFileTag .= unpackFS (srcSpanFile spn) + , srcSpanStartLineTag .= srcSpanStartLine spn + , srcSpanStartColTag .= srcSpanStartCol spn + , srcSpanEndLineTag .= srcSpanEndLine spn + , srcSpanEndColTag .= srcSpanEndCol spn + ] + +instance FromJSON RealSrcSpan where + parseJSON = withObject "object" $ \obj -> do + file <- fromString <$> (obj .: srcSpanFileTag) + mkRealSrcSpan + <$> (mkRealSrcLoc file + <$> obj .: srcSpanStartLineTag + <*> obj .: srcSpanStartColTag + ) + <*> (mkRealSrcLoc file + <$> obj .: srcSpanEndLineTag + <*> obj .: srcSpanEndColTag + ) + instance NFData Type where rnf = rwhnf diff --git a/ghcide/test/data/hover/GotoHover.hs b/ghcide/test/data/hover/GotoHover.hs index 80931a613a2..ae261c6bdfa 100644 --- a/ghcide/test/data/hover/GotoHover.hs +++ b/ghcide/test/data/hover/GotoHover.hs @@ -1,4 +1,4 @@ -{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE OverloadedStrings, TemplateHaskell #-} {- HLINT ignore -} module GotoHover ( module GotoHover) where import Data.Text (Text, pack) @@ -56,5 +56,8 @@ outer = undefined inner where imported :: Bar imported = foo +aa2 :: Bool +aa2 = $(id [| True |]) + hole :: Int hole = _ diff --git a/ghcide/test/exe/Main.hs b/ghcide/test/exe/Main.hs index 4d9dacd600d..e3cfb1d17e3 100644 --- a/ghcide/test/exe/Main.hs +++ b/ghcide/test/exe/Main.hs @@ -2520,7 +2520,7 @@ findDefinitionAndHoverTests = let , testGroup "hover" $ mapMaybe snd tests , checkFileCompiles sourceFilePath $ expectDiagnostics - [ ( "GotoHover.hs", [(DsError, (59, 7), "Found hole: _")]) ] + [ ( "GotoHover.hs", [(DsError, (62, 7), "Found hole: _")]) ] , testGroup "type-definition" typeDefinitionTests ] typeDefinitionTests = [ tst (getTypeDefinitions, checkDefs) aaaL14 (pure tcData) "Saturated data con" @@ -2570,10 +2570,11 @@ findDefinitionAndHoverTests = let lstL43 = Position 47 12 ; litL = [ExpectHoverText ["[8391 :: Int, 6268]"]] outL45 = Position 49 3 ; outSig = [ExpectHoverText ["outer", "Bool"], mkR 46 0 46 5] innL48 = Position 52 5 ; innSig = [ExpectHoverText ["inner", "Char"], mkR 49 2 49 7] - holeL60 = Position 59 7 ; hleInfo = [ExpectHoverText ["_ ::"]] + holeL60 = Position 62 7 ; hleInfo = [ExpectHoverText ["_ ::"]] cccL17 = Position 17 16 ; docLink = [ExpectHoverText ["[Documentation](file:///"]] imported = Position 56 13 ; importedSig = getDocUri "Foo.hs" >>= \foo -> return [ExpectHoverText ["foo", "Foo", "Haddock"], mkL foo 5 0 5 3] reexported = Position 55 14 ; reexportedSig = getDocUri "Bar.hs" >>= \bar -> return [ExpectHoverText ["Bar", "Bar", "Haddock"], mkL bar 3 0 3 14] + thLocL57 = Position 59 10 ; thLoc = [ExpectHoverText ["Identity"]] in mkFindTests -- def hover look expect @@ -2620,6 +2621,7 @@ findDefinitionAndHoverTests = let , test no skip cccL17 docLink "Haddock html links" , testM yes yes imported importedSig "Imported symbol" , testM yes yes reexported reexportedSig "Imported symbol (reexported)" + , test no yes thLocL57 thLoc "TH Splice Hover" ] where yes, broken :: (TestTree -> Maybe TestTree) yes = Just -- test should run and pass diff --git a/haskell-language-server.cabal b/haskell-language-server.cabal index ce7166f7daa..b20d9ba47f0 100644 --- a/haskell-language-server.cabal +++ b/haskell-language-server.cabal @@ -127,6 +127,11 @@ flag pragmas default: False manual: True +flag splice + description: Enable splice plugin + default: False + manual: True + -- formatters flag floskell @@ -201,6 +206,11 @@ common pragmas other-modules: Ide.Plugin.Pragmas cpp-options: -Dpragmas +common splice + if flag(splice) || flag(all-plugins) + build-depends: hls-splice-plugin + cpp-options: -Dsplice + -- formatters common floskell @@ -251,6 +261,7 @@ executable haskell-language-server , hlint , moduleName , pragmas + , splice , floskell , fourmolu , ormolu @@ -384,8 +395,9 @@ test-suite func-test , tasty-ant-xml >=1.1.6 , tasty-golden , tasty-rerun + , ghcide - hs-source-dirs: test/functional plugins/tactics/src plugins/hls-eval-plugin/test + hs-source-dirs: test/functional plugins/tactics/src plugins/hls-eval-plugin/test plugins/hls-splice-plugin/src main-is: Main.hs other-modules: @@ -410,6 +422,8 @@ test-suite func-test Symbol TypeDefinition Tactic + Splice + Ide.Plugin.Splice.Types Ide.Plugin.Tactic.TestTypes ghc-options: diff --git a/hie-cabal.yaml b/hie-cabal.yaml index 336cbe26b42..9e9a370ad1c 100644 --- a/hie-cabal.yaml +++ b/hie-cabal.yaml @@ -103,6 +103,9 @@ cradle: - path: "./hls-plugin-api/src" component: "lib:hls-plugin-api" + - path: "./hls-exactprint-utils/src" + component: "lib:hls-exactprint-utils" + - path: "./plugins/hls-class-plugin/src" component: "lib:hls-class-plugin" @@ -118,6 +121,9 @@ cradle: - path: "./plugins/hls-retrie-plugin/src" component: "lib:hls-retrie-plugin" + - path: "./plugins/hls-splice-plugin/src" + component: "hls-splice-plugin:lib" + - path: "./plugins/tactics/src" component: "lib:hls-tactics-plugin" diff --git a/hie-stack.yaml b/hie-stack.yaml index 2891e08e8fb..2b91638d471 100644 --- a/hie-stack.yaml +++ b/hie-stack.yaml @@ -40,6 +40,9 @@ cradle: - path: "./hls-plugin-api/src" component: "hls-plugin-api:lib" + - path: "./hls-exactprint-utils/src" + component: "hls-exactprint-utils:lib" + # Plugins: - path: "./plugins/hls-class-plugin/src" @@ -57,6 +60,9 @@ cradle: - path: "./plugins/hls-retrie-plugin/src" component: "hls-retrie-plugin:lib" + - path: "./plugins/hls-splice-plugin/src" + component: "hls-splice-plugin:lib" + - path: "./plugins/tactics/src" component: "hls-tactics-plugin:lib" diff --git a/hls-exactprint-utils/hls-exactprint-utils.cabal b/hls-exactprint-utils/hls-exactprint-utils.cabal new file mode 100644 index 00000000000..9149b900a8f --- /dev/null +++ b/hls-exactprint-utils/hls-exactprint-utils.cabal @@ -0,0 +1,48 @@ +cabal-version: 2.2 +name: hls-exactprint-utils +version: 0.5.0.1 +synopsis: Common residence package of ExactPrint related tree-transformation utilities for HLS plugins (ported from tactics plugin). +description: + Please see the README on GitHub at +homepage: https://github.com/haskell/haskell-language-server/hls-exactprint-utils +bug-reports: https://github.com/haskell/haskell-language-server/issues +author: Sandy Maguire, Reed Mullanix +maintainer: sandy@sandymaguire.me +copyright: Sandy Maguire, Reed Mullanix +category: Web +build-type: Simple + +flag pedantic + description: Enable -Werror + default: False + manual: True + +source-repository head + type: git + location: https://github.com/haskell/haskell-language-server + +library + exposed-modules: + Ide.TreeTransform + + hs-source-dirs: src + build-depends: + base >=4.12 && <5 + , dlist + , ghc + , ghc-exactprint + , ghcide + , haskell-lsp-types + , hls-plugin-api + , retrie + , syb + , text + , transformers + + + ghc-options: -Wall -Wredundant-constraints -Wincomplete-uni-patterns + + if flag(pedantic) + ghc-options: -Werror + + default-language: Haskell2010 diff --git a/hls-exactprint-utils/src/Ide/TreeTransform.hs b/hls-exactprint-utils/src/Ide/TreeTransform.hs new file mode 100644 index 00000000000..9d442744bcd --- /dev/null +++ b/hls-exactprint-utils/src/Ide/TreeTransform.hs @@ -0,0 +1,334 @@ +{-# LANGUAGE CPP #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE GeneralizedNewtypeDeriving #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} + +module Ide.TreeTransform + ( Graft(..), + graft, + graftDecls, + graftDeclsWithM, + hoistGraft, + graftWithM, + graftWithSmallestM, + transform, + transformM, + useAnnotatedSource, + annotateParsedSource, + ASTElement (..), + ExceptStringT (..), + ) +where + +import BasicTypes (appPrec) +import Control.Applicative (Alternative) +import Control.Monad +import qualified Control.Monad.Fail as Fail +import Control.Monad.IO.Class (MonadIO) +import Control.Monad.Trans.Class +import Control.Monad.Trans.Except +import Control.Monad.Zip +import qualified Data.DList as DL +import Data.Functor.Classes +import Data.Functor.Contravariant +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 (Outputable, ppr, showSDoc) +import Retrie.ExactPrint hiding (parseDecl, parseExpr, parsePattern, parseType) +#if __GLASGOW_HASKELL__ == 808 +import Control.Arrow +#endif + + +------------------------------------------------------------------------------ + +-- | Get the latest version of the annotated parse source. +useAnnotatedSource :: + String -> + IdeState -> + NormalizedFilePath -> + IO (Maybe (Annotated ParsedSource)) +useAnnotatedSource herald state nfp = + fmap annotateParsedSource + <$> runAction herald state (use GetParsedModule nfp) + +annotateParsedSource :: ParsedModule -> Annotated ParsedSource +annotateParsedSource = fixAnns + +------------------------------------------------------------------------------ + +{- | A transformation for grafting source trees together. Use the semigroup + instance to combine 'Graft's, and run them via 'transform'. +-} +newtype Graft m a = Graft + { runGraft :: DynFlags -> a -> TransformT m a + } + +hoistGraft :: (forall x. m x -> n x) -> Graft m a -> Graft n a +hoistGraft h (Graft f) = Graft (fmap (hoistTransform h) . f) + +newtype ExceptStringT m a = ExceptStringT {runExceptString :: ExceptT String m a} + deriving newtype + ( MonadTrans + , Monad + , Functor + , Applicative + , Alternative + , Foldable + , Contravariant + , MonadIO + , Eq1 + , Ord1 + , Show1 + , Read1 + , MonadZip + , MonadPlus + , Eq + , Ord + , Show + , Read + ) + +instance Monad m => Fail.MonadFail (ExceptStringT m) where + fail = ExceptStringT . ExceptT . pure . Left + +instance Monad m => Semigroup (Graft m a) where + Graft a <> Graft b = Graft $ \dflags -> a dflags >=> b dflags + +instance Monad m => Monoid (Graft m a) where + mempty = Graft $ const pure + +------------------------------------------------------------------------------ + +-- | Convert a 'Graft' into a 'WorkspaceEdit'. +transform :: + DynFlags -> + ClientCapabilities -> + Uri -> + Graft (Either String) 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 + +------------------------------------------------------------------------------ + +-- | Convert a 'Graft' into a 'WorkspaceEdit'. +transformM :: + Monad m => + DynFlags -> + ClientCapabilities -> + Uri -> + Graft (ExceptStringT m) ParsedSource -> + Annotated ParsedSource -> + m (Either String WorkspaceEdit) +transformM dflags ccs uri f a = runExceptT $ + runExceptString $ 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 ast a. + (Data a, ASTElement ast) => + SrcSpan -> + Located ast -> + Graft (Either String) a +graft dst val = Graft $ \dflags a -> do + (anns, val') <- annotate dflags $ maybeParensAST val + modifyAnnsT $ mappend anns + pure $ + everywhere' + ( mkT $ + \case + (L src _ :: Located ast) | src == dst -> val' + l -> l + ) + a + +------------------------------------------------------------------------------ + +graftWithM :: + forall ast m a. + (Fail.MonadFail m, Data a, ASTElement ast) => + SrcSpan -> + (Located ast -> TransformT m (Maybe (Located ast))) -> + Graft m a +graftWithM dst trans = Graft $ \dflags a -> do + everywhereM' + ( mkM $ + \case + val@(L src _ :: Located ast) + | src == dst -> do + mval <- trans val + case mval of + Just val' -> do + (anns, val'') <- + hoistTransform (either Fail.fail pure) $ + annotate dflags $ maybeParensAST val' + modifyAnnsT $ mappend anns + pure val'' + Nothing -> pure val + l -> pure l + ) + a + +graftWithSmallestM :: + forall ast m a. + (Fail.MonadFail m, Data a, ASTElement ast) => + SrcSpan -> + (Located ast -> TransformT m (Maybe (Located ast))) -> + Graft m a +graftWithSmallestM dst trans = Graft $ \dflags a -> do + everywhereM' + ( mkM $ + \case + val@(L src _ :: Located ast) + | dst `isSubspanOf` src -> do + mval <- trans val + case mval of + Just val' -> do + (anns, val'') <- + hoistTransform (either Fail.fail pure) $ + annotate dflags $ maybeParensAST val' + modifyAnnsT $ mappend anns + pure val'' + Nothing -> pure val + l -> pure l + ) + a + +graftDecls :: + forall a. + (HasDecls a) => + SrcSpan -> + [LHsDecl GhcPs] -> + Graft (Either String) a +graftDecls dst decs0 = Graft $ \dflags a -> do + decs <- forM decs0 $ \decl -> do + (anns, decl') <- annotateDecl dflags decl + modifyAnnsT $ mappend anns + pure decl' + let go [] = DL.empty + go (L src e : rest) + | src == dst = DL.fromList decs <> DL.fromList rest + | otherwise = DL.singleton (L src e) <> go rest + modifyDeclsT (pure . DL.toList . go) a + +graftDeclsWithM :: + forall a m. + (HasDecls a, Fail.MonadFail m) => + SrcSpan -> + (LHsDecl GhcPs -> TransformT m (Maybe [LHsDecl GhcPs])) -> + Graft m a +graftDeclsWithM dst toDecls = Graft $ \dflags a -> do + let go [] = pure DL.empty + go (e@(L src _) : rest) + | src == dst = toDecls e >>= \case + Just decs0 -> do + decs <- forM decs0 $ \decl -> do + (anns, decl') <- + hoistTransform (either Fail.fail pure) $ + annotateDecl dflags decl + modifyAnnsT $ mappend anns + pure decl' + pure $ DL.fromList decs <> DL.fromList rest + Nothing -> (DL.singleton e <>) <$> go rest + | otherwise = (DL.singleton e <>) <$> go rest + modifyDeclsT (fmap DL.toList . go) a + + +everywhereM' :: forall m. Monad m => GenericM m -> GenericM m +everywhereM' f = go + where + go :: GenericM m + go = gmapM go <=< f + +class (Data ast, Outputable ast) => ASTElement ast where + parseAST :: Parser (Located ast) + maybeParensAST :: Located ast -> Located ast + +instance p ~ GhcPs => ASTElement (HsExpr p) where + parseAST = parseExpr + maybeParensAST = parenthesize + +instance p ~ GhcPs => ASTElement (Pat p) where +#if __GLASGOW_HASKELL__ == 808 + parseAST = fmap (fmap $ right $ second dL) . parsePattern + maybeParensAST = dL . parenthesizePat appPrec . unLoc +#else + parseAST = parsePattern + maybeParensAST = parenthesizePat appPrec +#endif + +instance p ~ GhcPs => ASTElement (HsType p) where + parseAST = parseType + maybeParensAST = parenthesizeHsType appPrec + +instance p ~ GhcPs => ASTElement (HsDecl p) where + parseAST = parseDecl + maybeParensAST = id + +------------------------------------------------------------------------------ + +-- | 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 :: ASTElement ast => DynFlags -> Located ast -> TransformT (Either String) (Anns, Located ast) +annotate dflags ast = do + uniq <- show <$> uniqueSrcSpanT + let rendered = render dflags ast + (anns, expr') <- lift $ either (Left . show) Right $ parseAST dflags uniq rendered + let anns' = setPrecedingLines expr' 0 1 anns + pure (anns', expr') + +-- | Given an 'LHsDecl', compute its exactprint annotations. +annotateDecl :: DynFlags -> LHsDecl GhcPs -> TransformT (Either String) (Anns, LHsDecl GhcPs) +annotateDecl dflags ast = do + uniq <- show <$> uniqueSrcSpanT + let rendered = render dflags ast + (anns, expr') <- lift $ either (Left . show) Right $ parseDecl dflags uniq rendered + let anns' = setPrecedingLines expr' 1 0 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 diff --git a/nix/default.nix b/nix/default.nix index 17e424f1185..a4fc866ca74 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -20,11 +20,13 @@ let shake-bench = gitignoreSource ../shake-bench; hie-compat = gitignoreSource ../hie-compat; hls-plugin-api = gitignoreSource ../hls-plugin-api; + hls-exactprint-utils = gitignoreSource ../hls-exactprint-utils; hls-class-plugin = gitignoreSource ../plugins/hls-class-plugin; hls-eval-plugin = gitignoreSource ../plugins/hls-eval-plugin; hls-explicit-imports-plugin = gitignoreSource ../plugins/hls-explicit-imports-plugin; hls-hlint-plugin = gitignoreSource ../plugins/hls-hlint-plugin; hls-retrie-plugin = gitignoreSource ../plugins/hls-retrie-plugin; + hls-splice-plugin = gitignoreSource ../plugins/hls-splice-plugin; hls-tactics-plugin = gitignoreSource ../plugins/tactics; }); in diff --git a/plugins/hls-splice-plugin/LICENSE b/plugins/hls-splice-plugin/LICENSE new file mode 100644 index 00000000000..261eeb9e9f8 --- /dev/null +++ b/plugins/hls-splice-plugin/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/plugins/hls-splice-plugin/hls-splice-plugin.cabal b/plugins/hls-splice-plugin/hls-splice-plugin.cabal new file mode 100644 index 00000000000..41d7a0e3f28 --- /dev/null +++ b/plugins/hls-splice-plugin/hls-splice-plugin.cabal @@ -0,0 +1,36 @@ +cabal-version: 2.2 +name: hls-splice-plugin +version: 0.1.0.0 +synopsis: HLS Plugin Expanding +license: Apache-2.0 +license-file: LICENSE +author: Hiromi ISHII +maintainer: konn.jinro_at_gmail.com +category: Development +build-type: Simple + +library + exposed-modules: Ide.Plugin.Splice + ghc-options: -Wall + other-modules: Ide.Plugin.Splice.Types + hs-source-dirs: src + build-depends: aeson + , base + , containers + , foldl + , haskell-lsp + , hls-plugin-api + , ghc + , ghc-exactprint + , hls-exactprint-utils + , ghcide + , lens + , dlist + , retrie + , shake + , syb + , text + , transformers + , unordered-containers + + default-language: Haskell2010 diff --git a/plugins/hls-splice-plugin/src/Ide/Plugin/Splice.hs b/plugins/hls-splice-plugin/src/Ide/Plugin/Splice.hs new file mode 100644 index 00000000000..c77ac1dc887 --- /dev/null +++ b/plugins/hls-splice-plugin/src/Ide/Plugin/Splice.hs @@ -0,0 +1,473 @@ +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE CPP #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE DerivingStrategies #-} +{-# LANGUAGE DuplicateRecordFields #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE LambdaCase #-} +{-# LANGUAGE MagicHash #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE RankNTypes #-} +{-# LANGUAGE RecordWildCards #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE TupleSections #-} +{-# LANGUAGE TypeApplications #-} +{-# LANGUAGE TypeFamilies #-} +{-# LANGUAGE ViewPatterns #-} + +module Ide.Plugin.Splice + ( descriptor, + ) +where + +import Control.Applicative (Alternative ((<|>))) +import Control.Arrow +import qualified Control.Foldl as L +import Control.Lens (ix, view, (%~), (<&>), (^.)) +import Control.Monad +import qualified Control.Monad.Fail as Fail +import Control.Monad.Trans.Class +import Control.Monad.Trans.Except +import Control.Monad.Trans.Maybe +import Data.Aeson +import Data.Function +import Data.Generics +import qualified Data.Kind as Kinds +import Data.List (sortOn) +import Data.Maybe (fromMaybe, listToMaybe, mapMaybe) +import qualified Data.Text as T +import Development.IDE +import Development.IDE.GHC.Compat hiding (getLoc) +import Exception +import GHC.Exts +import GhcMonad +import GhcPlugins hiding (Var, getLoc, (<>)) +import Ide.Plugin.Splice.Types +import Ide.PluginUtils (mkLspCommand, responseError) +import Ide.TreeTransform +import Ide.Types +import Language.Haskell.GHC.ExactPrint (TransformT, setPrecedingLines, uniqueSrcSpanT) +import Language.Haskell.LSP.Core +import Language.Haskell.LSP.Messages +import Language.Haskell.LSP.Types +import qualified Language.Haskell.LSP.Types.Lens as J +import Retrie.ExactPrint (Annotated) +import RnSplice +import TcRnMonad +import Data.Foldable (Foldable(foldl')) + +descriptor :: PluginId -> PluginDescriptor IdeState +descriptor plId = + (defaultPluginDescriptor plId) + { pluginCommands = commands + , pluginCodeActionProvider = Just codeAction + } + +commands :: [PluginCommand IdeState] +commands = + [ PluginCommand expandInplaceId inplaceCmdName $ expandTHSplice Inplace + -- , PluginCommand expandCommentedId commentedCmdName $ expandTHSplice Commented + ] + +newtype SubSpan = SubSpan {runSubSpan :: SrcSpan} + +instance Eq SubSpan where + (==) = (==) `on` runSubSpan + +instance Ord SubSpan where + (<=) = coerce isSubspanOf + +expandTHSplice :: + -- | Inplace? + ExpandStyle -> + CommandFunction IdeState ExpandSpliceParams +expandTHSplice _eStyle lsp ideState params@ExpandSpliceParams {..} = + fmap (fromMaybe defaultResult) $ + runMaybeT $ do + + fp <- MaybeT $ pure $ uriToNormalizedFilePath $ toNormalizedUri uri + eedits <- + ( lift . runExceptT . withTypeChecked fp + =<< MaybeT + (runAction "expandTHSplice.TypeCheck" ideState $ use TypeCheck fp) + ) + <|> lift (runExceptT $ expandManually fp) + + case eedits of + Left err -> do + reportEditor + lsp + MtError + ["Error during expanding splice: " <> T.pack err] + pure (Left $ responseError $ T.pack err, Nothing) + Right edits -> + pure + ( Right Null + , Just (WorkspaceApplyEdit, ApplyWorkspaceEditParams edits) + ) + where + range = realSrcSpanToRange spliceSpan + srcSpan = RealSrcSpan spliceSpan + defaultResult = (Right Null, Nothing) + expandManually fp = do + mresl <- + liftIO $ runAction "expandTHSplice.fallback.TypeCheck (stale)" ideState $ useWithStale TypeCheck fp + (TcModuleResult {..}, _) <- + maybe + (throwE "Splice expansion: Type-checking information not found in cache.\nYou can once delete or replace the macro with placeholder, convince the type checker and then revert to original (errornous) macro and expand splice again." + ) + pure mresl + reportEditor + lsp + MtWarning + [ "Expansion in type-chcking phase failed;" + , "trying to expand manually, but note taht it is less rigorous." + ] + pm <- + liftIO $ + runAction "expandTHSplice.fallback.GetParsedModule" ideState $ + use_ GetParsedModule fp + (ps, hscEnv, _dflags) <- setupHscEnv ideState fp pm + + manualCalcEdit + lsp + range + ps + hscEnv + tmrTypechecked + spliceSpan + _eStyle + params + withTypeChecked fp TcModuleResult {..} = do + (ps, _hscEnv, dflags) <- setupHscEnv ideState fp tmrParsed + let Splices {..} = tmrTopLevelSplices + let exprSuperSpans = + listToMaybe $ findSubSpansDesc srcSpan exprSplices + _patSuperSpans = +#if __GLASGOW_HASKELL__ == 808 + fmap (second dL) $ +#endif + listToMaybe $ findSubSpansDesc srcSpan patSplices + typeSuperSpans = + listToMaybe $ findSubSpansDesc srcSpan typeSplices + declSuperSpans = + listToMaybe $ findSubSpansDesc srcSpan declSplices + + graftSpliceWith :: + forall ast. + HasSplice ast => + Maybe (SrcSpan, Located (ast GhcPs)) -> + Maybe (Either String WorkspaceEdit) + graftSpliceWith expandeds = + expandeds <&> \(_, expanded) -> + transform + dflags + (clientCapabilities lsp) + uri + (graft (RealSrcSpan spliceSpan) expanded) + ps + maybe (throwE "No splice information found") (either throwE pure) $ + case spliceContext of + Expr -> graftSpliceWith exprSuperSpans + Pat -> + + graftSpliceWith _patSuperSpans + + HsType -> graftSpliceWith typeSuperSpans + HsDecl -> + declSuperSpans <&> \(_, expanded) -> + transform + dflags + (clientCapabilities lsp) + uri + (graftDecls (RealSrcSpan spliceSpan) expanded) + ps + <&> + -- FIXME: Why ghc-exactprint sweeps preceeding comments? + adjustToRange uri range + +setupHscEnv + :: IdeState + -> NormalizedFilePath + -> ParsedModule + -> ExceptT String IO (Annotated ParsedSource, HscEnv, DynFlags) +setupHscEnv ideState fp pm = do + hscEnvEq <- + liftIO $ + runAction "expandTHSplice.fallback.ghcSessionDeps" ideState $ + use_ GhcSessionDeps fp + let ps = annotateParsedSource pm + hscEnv0 = hscEnvWithImportPaths hscEnvEq + modSum = pm_mod_summary pm + df' <- liftIO $ setupDynFlagsForGHCiLike hscEnv0 $ ms_hspp_opts modSum + let hscEnv = hscEnv0 { hsc_dflags = df' } + pure (ps, hscEnv, df') + +setupDynFlagsForGHCiLike :: HscEnv -> DynFlags -> IO DynFlags +setupDynFlagsForGHCiLike env dflags = do + let dflags3 = + dflags + { hscTarget = HscInterpreted + , ghcMode = CompManager + , ghcLink = LinkInMemory + } + platform = targetPlatform dflags3 + dflags3a = updateWays $ dflags3 {ways = interpWays} + dflags3b = + foldl gopt_set dflags3a $ + concatMap (wayGeneralFlags platform) interpWays + dflags3c = + foldl gopt_unset dflags3b $ + concatMap (wayUnsetGeneralFlags platform) interpWays + dflags4 = + dflags3c + `gopt_set` Opt_ImplicitImportQualified + `gopt_set` Opt_IgnoreOptimChanges + `gopt_set` Opt_IgnoreHpcChanges + `gopt_unset` Opt_DiagnosticsShowCaret + initializePlugins env dflags4 + +adjustToRange :: Uri -> Range -> WorkspaceEdit -> WorkspaceEdit +adjustToRange uri ran (WorkspaceEdit mhult mlt) = + WorkspaceEdit (adjustWS <$> mhult) (fmap adjustDoc <$> mlt) + where + adjustTextEdits :: Traversable f => f TextEdit -> f TextEdit + adjustTextEdits eds = + let Just minStart = + L.fold + (L.premap (view J.range) L.minimum) + eds + in adjustLine minStart <$> eds + adjustWS = ix uri %~ adjustTextEdits + adjustDoc es + | es ^. J.textDocument . J.uri == uri = + es & J.edits %~ adjustTextEdits + | otherwise = es + + adjustLine :: Range -> TextEdit -> TextEdit + adjustLine bad = + J.range %~ \r -> + if r == bad then ran else bad + +findSubSpansDesc :: SrcSpan -> [(LHsExpr GhcTc, a)] -> [(SrcSpan, a)] +findSubSpansDesc srcSpan = + sortOn (Down . SubSpan . fst) + . mapMaybe + ( \(L spn _, e) -> do + guard (spn `isSubspanOf` srcSpan) + pure (spn, e) + ) + +data SpliceClass where + OneToOneAST :: HasSplice ast => Proxy# ast -> SpliceClass + IsHsDecl :: SpliceClass + +class (Outputable (ast GhcRn), ASTElement (ast GhcPs)) => HasSplice ast where + type SpliceOf ast :: Kinds.Type -> Kinds.Type + type SpliceOf ast = HsSplice + matchSplice :: Proxy# ast -> ast GhcPs -> Maybe (SpliceOf ast GhcPs) + expandSplice :: Proxy# ast -> SpliceOf ast GhcPs -> RnM (Either (ast GhcPs) (ast GhcRn), FreeVars) + +instance HasSplice HsExpr where + matchSplice _ (HsSpliceE _ spl) = Just spl + matchSplice _ _ = Nothing + expandSplice _ = fmap (first Right) . rnSpliceExpr + +instance HasSplice Pat where + matchSplice _ (SplicePat _ spl) = Just spl + matchSplice _ _ = Nothing + expandSplice _ = rnSplicePat + + +instance HasSplice HsType where + matchSplice _ (HsSpliceTy _ spl) = Just spl + matchSplice _ _ = Nothing + expandSplice _ = fmap (first Right) . rnSpliceType + +classifyAST :: SpliceContext -> SpliceClass +classifyAST = \case + Expr -> OneToOneAST @HsExpr proxy# + HsDecl -> IsHsDecl + Pat -> OneToOneAST @Pat proxy# + HsType -> OneToOneAST @HsType proxy# + +reportEditor :: MonadIO m => LspFuncs a -> MessageType -> [T.Text] -> m () +reportEditor lsp msgTy msgs = + liftIO $ + sendFunc lsp $ + NotShowMessage $ + NotificationMessage "2.0" WindowShowMessage $ + ShowMessageParams msgTy $ + T.unlines msgs + +manualCalcEdit :: + LspFuncs a -> + Range -> + Annotated ParsedSource -> + HscEnv -> + TcGblEnv -> + RealSrcSpan -> + ExpandStyle -> + ExpandSpliceParams -> + ExceptT String IO WorkspaceEdit +manualCalcEdit lsp ran ps hscEnv typechkd srcSpan _eStyle ExpandSpliceParams {..} = do + (warns, resl) <- + ExceptT $ do + ((warns, errs), eresl) <- + initTcWithGbl hscEnv typechkd srcSpan $ + case classifyAST spliceContext of + IsHsDecl -> fmap (fmap $ adjustToRange uri ran) $ + flip (transformM dflags (clientCapabilities lsp) uri) ps $ + graftDeclsWithM (RealSrcSpan srcSpan) $ \case + (L _spn (SpliceD _ (SpliceDecl _ (L _ spl) _))) -> do + eExpr <- + either (fail . show) pure + =<< lift + ( lift $ + gtry @_ @SomeException $ + (fst <$> rnTopSpliceDecls spl) + ) + pure $ Just eExpr + _ -> pure Nothing + OneToOneAST astP -> + flip (transformM dflags (clientCapabilities lsp) uri) ps $ + graftWithM (RealSrcSpan srcSpan) $ \case + (L _spn (matchSplice astP -> Just spl)) -> do + eExpr <- + either (fail . show) pure + =<< lift + ( lift $ + gtry @_ @SomeException $ + (fst <$> expandSplice astP spl) + ) + Just <$> either (pure . L _spn) (unRenamedE dflags) eExpr + _ -> pure Nothing + pure $ (warns,) <$> fromMaybe (Left $ show errs) eresl + + unless + (null warns) + $ reportEditor + lsp + MtWarning + [ "Warning during expanding: " + , "" + , T.pack (show warns) + ] + pure resl + where + dflags = hsc_dflags hscEnv + +-- | FIXME: Is thereAny "clever" way to do this exploiting TTG? +unRenamedE :: + forall ast m. + (Fail.MonadFail m, HasSplice ast) => + DynFlags -> + ast GhcRn -> + TransformT m (Located (ast GhcPs)) +unRenamedE dflags expr = do + uniq <- show <$> uniqueSrcSpanT + (anns, expr') <- + either (fail . show) pure $ + parseAST @(ast GhcPs) dflags uniq $ + showSDoc dflags $ ppr expr + let _anns' = setPrecedingLines expr' 0 1 anns + pure expr' + +data SearchResult r = + Continue | Stop | Here r + deriving (Read, Show, Eq, Ord, Data, Typeable) + +fromSearchResult :: SearchResult a -> Maybe a +fromSearchResult (Here r) = Just r +fromSearchResult _ = Nothing + +-- TODO: workaround when HieAst unavailable (e.g. when the module itself errors) +-- TODO: Declaration Splices won't appear in HieAst; perhaps we must just use Parsed/Renamed ASTs? +codeAction :: CodeActionProvider IdeState +codeAction _ state plId docId ran _ = + fmap (maybe (Right $ List []) Right) $ + runMaybeT $ do + fp <- MaybeT $ pure $ uriToNormalizedFilePath $ toNormalizedUri theUri + ParsedModule {..} <- + MaybeT . runAction "splice.codeAction.GitHieAst" state $ + use GetParsedModule fp + let spn = + rangeToRealSrcSpan ran $ + fromString $ + fromNormalizedFilePath fp + mouterSplice = something' (detectSplice spn) pm_parsed_source + mcmds <- forM mouterSplice $ + \(spliceSpan, spliceContext) -> + forM expandStyles $ \(_, (title, cmdId)) -> do + let params = ExpandSpliceParams {uri = theUri, ..} + act <- liftIO $ mkLspCommand plId cmdId title (Just [toJSON params]) + pure $ + CACodeAction $ + CodeAction title (Just CodeActionRefactorRewrite) Nothing Nothing (Just act) + + pure $ maybe mempty List mcmds + where + theUri = docId ^. J.uri + detectSplice :: + RealSrcSpan -> + GenericQ (SearchResult (RealSrcSpan, SpliceContext)) + detectSplice spn = + mkQ + Continue + ( \case + (L l@(RealSrcSpan spLoc) expr :: LHsExpr GhcPs) + | RealSrcSpan spn `isSubspanOf` l -> + case expr of + HsSpliceE {} -> Here (spLoc, Expr) + _ -> Continue + _ -> Stop + ) + `extQ` \case +#if __GLASGOW_HASKELL__ == 808 + (dL @(Pat GhcPs) -> L l@(RealSrcSpan spLoc) pat :: Located (Pat GhcPs)) +#else + (L l@(RealSrcSpan spLoc) pat :: LPat GhcPs) +#endif + | RealSrcSpan spn `isSubspanOf` l -> + case pat of + SplicePat{} -> Here (spLoc, Pat) + _ -> Continue + _ -> Stop + `extQ` \case + (L l@(RealSrcSpan spLoc) ty :: LHsType GhcPs) + | RealSrcSpan spn `isSubspanOf` l -> + case ty of + HsSpliceTy {} -> Here (spLoc, HsType) + _ -> Continue + _ -> Stop + `extQ` \case + (L l@(RealSrcSpan spLoc) decl :: LHsDecl GhcPs) + | RealSrcSpan spn `isSubspanOf` l -> + case decl of + SpliceD {} -> Here (spLoc, HsDecl) + _ -> Continue + _ -> Stop + +-- | Like 'something', but performs top-down searching, cutoffs when 'Stop' received, +-- and picks inenrmost result. +something' :: forall a. GenericQ (SearchResult a) -> GenericQ (Maybe a) +something' f = go + where + go :: GenericQ (Maybe a) + go x = + case f x of + Stop -> Nothing + resl -> foldl' (flip (<|>)) (fromSearchResult resl) (gmapQ go x) + +posToRealSrcLoc :: Position -> FastString -> RealSrcLoc +posToRealSrcLoc pos fs = mkRealSrcLoc fs (line + 1) (col + 1) + where + line = _line pos + col = _character pos + +rangeToRealSrcSpan :: Range -> FastString -> RealSrcSpan +rangeToRealSrcSpan ran fs = + mkRealSrcSpan + (posToRealSrcLoc (_start ran) fs) + (posToRealSrcLoc (_end ran) fs) diff --git a/plugins/hls-splice-plugin/src/Ide/Plugin/Splice/Types.hs b/plugins/hls-splice-plugin/src/Ide/Plugin/Splice/Types.hs new file mode 100644 index 00000000000..f44ba69d5ad --- /dev/null +++ b/plugins/hls-splice-plugin/src/Ide/Plugin/Splice/Types.hs @@ -0,0 +1,54 @@ +{-# LANGUAGE DeriveAnyClass #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DerivingStrategies #-} + +module Ide.Plugin.Splice.Types where + +import Data.Aeson (FromJSON, ToJSON) +import Development.IDE (Uri) +import GHC.Generics (Generic) +import Development.IDE.GHC.Compat (RealSrcSpan) +import qualified Data.Text as T +import Ide.Types ( CommandId ) + +-- | Parameter for the addMethods PluginCommand. +data ExpandSpliceParams = ExpandSpliceParams + { uri :: Uri + , spliceSpan :: RealSrcSpan + , spliceContext :: SpliceContext + } + deriving (Show, Eq, Generic) + deriving anyclass (ToJSON, FromJSON) + +-- FIXME: HsDecl needs different treatment of splicing. +data SpliceContext = Expr | HsDecl | Pat | HsType + deriving (Read, Show, Eq, Ord, Generic) + deriving anyclass (ToJSON, FromJSON) + +data ExpandStyle = Inplace | Commented + deriving (Read, Show, Eq, Ord, Generic) + +expandStyles :: [(ExpandStyle, (T.Text, CommandId))] +expandStyles = + [ (Inplace, (inplaceCmdName, expandInplaceId)) + -- , (Commented, commentedCmdName, expandCommentedId) + ] + +toExpandCmdTitle :: ExpandStyle -> T.Text +toExpandCmdTitle Inplace = inplaceCmdName +toExpandCmdTitle Commented = commentedCmdName + +toCommandId :: ExpandStyle -> CommandId +toCommandId Inplace = expandInplaceId +toCommandId Commented = expandCommentedId + +expandInplaceId, expandCommentedId :: CommandId +expandInplaceId = "expandTHSpliceInplace" +expandCommentedId = "expandTHSpliceCommented" + +inplaceCmdName :: T.Text +inplaceCmdName = "expand TemplateHaskell Splice (in-place)" + +commentedCmdName :: T.Text +commentedCmdName = "expand TemplateHaskell Splice (comented-out)" diff --git a/plugins/tactics/hls-tactics-plugin.cabal b/plugins/tactics/hls-tactics-plugin.cabal index aa1256c02cf..43d72ee12d9 100644 --- a/plugins/tactics/hls-tactics-plugin.cabal +++ b/plugins/tactics/hls-tactics-plugin.cabal @@ -37,7 +37,6 @@ library Ide.Plugin.Tactic.Tactics Ide.Plugin.Tactic.Types Ide.Plugin.Tactic.TestTypes - Ide.TreeTransform ghc-options: -Wno-name-shadowing -Wredundant-constraints @@ -60,6 +59,7 @@ library , ghcide >=0.1 , haskell-lsp ^>=0.22 , hls-plugin-api + , hls-exactprint-utils , lens , mtl , refinery ^>=0.3 diff --git a/plugins/tactics/src/Ide/TreeTransform.hs b/plugins/tactics/src/Ide/TreeTransform.hs deleted file mode 100644 index 80b0062ff58..00000000000 --- a/plugins/tactics/src/Ide/TreeTransform.hs +++ /dev/null @@ -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 - diff --git a/shell.nix b/shell.nix index 8c1a0f626a4..f0f5ce9b882 100644 --- a/shell.nix +++ b/shell.nix @@ -29,11 +29,13 @@ let defaultCompiler = "ghc" + lib.replaceStrings ["."] [""] haskellPackages.ghc. p.shake-bench p.hie-compat p.hls-plugin-api + p.hls-exactprint-utils p.hls-class-plugin p.hls-eval-plugin p.hls-explicit-imports-plugin p.hls-hlint-plugin p.hls-retrie-plugin + p.hls-splice-plugin p.hls-tactics-plugin ]; diff --git a/stack-8.10.1.yaml b/stack-8.10.1.yaml index 80d86db779c..bdce53c229e 100644 --- a/stack-8.10.1.yaml +++ b/stack-8.10.1.yaml @@ -6,11 +6,13 @@ packages: - ./ghcide/ # - ./shake-bench - ./hls-plugin-api +- ./hls-exactprint-utils - ./plugins/hls-class-plugin - ./plugins/hls-eval-plugin - ./plugins/hls-explicit-imports-plugin - ./plugins/hls-hlint-plugin - ./plugins/hls-retrie-plugin +- ./plugins/hls-splice-plugin - ./plugins/tactics ghc-options: diff --git a/stack-8.10.2.yaml b/stack-8.10.2.yaml index 6b366dfbdd2..90abc17483f 100644 --- a/stack-8.10.2.yaml +++ b/stack-8.10.2.yaml @@ -5,12 +5,14 @@ packages: - ./hie-compat - ./ghcide/ - ./hls-plugin-api + - ./hls-exactprint-utils # - ./shake-bench - ./plugins/hls-class-plugin - ./plugins/hls-eval-plugin - ./plugins/hls-explicit-imports-plugin - ./plugins/hls-hlint-plugin - ./plugins/hls-retrie-plugin + - ./plugins/hls-splice-plugin - ./plugins/tactics ghc-options: diff --git a/stack-8.10.3.yaml b/stack-8.10.3.yaml index 25e791c006a..8375d928e8e 100644 --- a/stack-8.10.3.yaml +++ b/stack-8.10.3.yaml @@ -5,12 +5,14 @@ packages: - ./hie-compat - ./ghcide/ - ./hls-plugin-api + - ./hls-exactprint-utils # - ./shake-bench - ./plugins/hls-class-plugin - ./plugins/hls-eval-plugin - ./plugins/hls-explicit-imports-plugin - ./plugins/hls-hlint-plugin - ./plugins/hls-retrie-plugin + - ./plugins/hls-splice-plugin - ./plugins/tactics ghc-options: diff --git a/stack-8.6.4.yaml b/stack-8.6.4.yaml index 1cdd6507b89..0712f2b50cb 100644 --- a/stack-8.6.4.yaml +++ b/stack-8.6.4.yaml @@ -7,11 +7,13 @@ packages: - ./ghcide/ # - ./shake-bench - ./hls-plugin-api + - ./hls-exactprint-utils - ./plugins/hls-class-plugin - ./plugins/hls-eval-plugin - ./plugins/hls-explicit-imports-plugin - ./plugins/hls-hlint-plugin - ./plugins/hls-retrie-plugin + - ./plugins/hls-splice-plugin - ./plugins/tactics ghc-options: diff --git a/stack-8.6.5.yaml b/stack-8.6.5.yaml index fa805f9276e..debcbf65ea5 100644 --- a/stack-8.6.5.yaml +++ b/stack-8.6.5.yaml @@ -5,12 +5,14 @@ packages: - ./hie-compat - ./ghcide/ - ./hls-plugin-api + - ./hls-exactprint-utils # - ./shake-bench - ./plugins/hls-class-plugin - ./plugins/hls-eval-plugin - ./plugins/hls-explicit-imports-plugin - ./plugins/hls-hlint-plugin - ./plugins/hls-retrie-plugin + - ./plugins/hls-splice-plugin - ./plugins/tactics ghc-options: diff --git a/stack-8.8.2.yaml b/stack-8.8.2.yaml index 6f12ce8aca7..975d195bcae 100644 --- a/stack-8.8.2.yaml +++ b/stack-8.8.2.yaml @@ -5,12 +5,14 @@ packages: - ./hie-compat - ./ghcide/ - ./hls-plugin-api + - ./hls-exactprint-utils - ./shake-bench - ./plugins/hls-class-plugin - ./plugins/hls-eval-plugin - ./plugins/hls-explicit-imports-plugin - ./plugins/hls-hlint-plugin - ./plugins/hls-retrie-plugin + - ./plugins/hls-splice-plugin - ./plugins/tactics ghc-options: diff --git a/stack-8.8.3.yaml b/stack-8.8.3.yaml index 57d65a386d6..4a0fccce640 100644 --- a/stack-8.8.3.yaml +++ b/stack-8.8.3.yaml @@ -6,11 +6,13 @@ packages: - ./ghcide/ - ./shake-bench - ./hls-plugin-api + - ./hls-exactprint-utils - ./plugins/hls-class-plugin - ./plugins/hls-eval-plugin - ./plugins/hls-explicit-imports-plugin - ./plugins/hls-hlint-plugin - ./plugins/hls-retrie-plugin + - ./plugins/hls-splice-plugin - ./plugins/tactics ghc-options: diff --git a/stack-8.8.4.yaml b/stack-8.8.4.yaml index 2e8f5bdab73..7805eb33e3a 100644 --- a/stack-8.8.4.yaml +++ b/stack-8.8.4.yaml @@ -6,11 +6,13 @@ packages: - ./ghcide/ - ./shake-bench - ./hls-plugin-api + - ./hls-exactprint-utils - ./plugins/hls-class-plugin - ./plugins/hls-eval-plugin - ./plugins/hls-explicit-imports-plugin - ./plugins/hls-hlint-plugin - ./plugins/hls-retrie-plugin + - ./plugins/hls-splice-plugin - ./plugins/tactics ghc-options: diff --git a/stack.yaml b/stack.yaml index fa805f9276e..debcbf65ea5 100644 --- a/stack.yaml +++ b/stack.yaml @@ -5,12 +5,14 @@ packages: - ./hie-compat - ./ghcide/ - ./hls-plugin-api + - ./hls-exactprint-utils # - ./shake-bench - ./plugins/hls-class-plugin - ./plugins/hls-eval-plugin - ./plugins/hls-explicit-imports-plugin - ./plugins/hls-hlint-plugin - ./plugins/hls-retrie-plugin + - ./plugins/hls-splice-plugin - ./plugins/tactics ghc-options: diff --git a/test/functional/Main.hs b/test/functional/Main.hs index 29c7c4785e4..ae458d788a5 100644 --- a/test/functional/Main.hs +++ b/test/functional/Main.hs @@ -19,6 +19,7 @@ import Progress import Reference import Rename import Symbol +import Splice import Tactic import Test.Tasty import Test.Tasty.Ingredients.Rerun @@ -58,4 +59,5 @@ main = , Symbol.tests , Tactic.tests , TypeDefinition.tests + , Splice.tests ] diff --git a/test/functional/Splice.hs b/test/functional/Splice.hs new file mode 100644 index 00000000000..e5fdca04687 --- /dev/null +++ b/test/functional/Splice.hs @@ -0,0 +1,139 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE ScopedTypeVariables #-} +{-# LANGUAGE ViewPatterns #-} +{-# LANGUAGE CPP #-} + +module Splice (tests) where + +import Control.Applicative.Combinators +import Control.Monad +import Control.Monad.IO.Class +import Data.List (find) +import Data.Text (Text) +import qualified Data.Text as T +import qualified Data.Text.IO as T +import Ide.Plugin.Splice.Types +import Language.Haskell.LSP.Test +import Language.Haskell.LSP.Types + ( ApplyWorkspaceEditRequest, + CAResult (..), + CodeAction (..), + Position (..), + Range (..), + TextDocumentContentChangeEvent (..), + TextEdit (..), + ) +import System.Directory +import System.FilePath +import System.Time.Extra (sleep) +import Test.Hls.Util +import Test.Tasty +import Test.Tasty.HUnit + +tests :: TestTree +tests = + testGroup + "splice" + [ goldenTest "TSimpleExp.hs" Inplace 6 15 + , goldenTest "TSimpleExp.hs" Inplace 6 24 + , goldenTest "TTypeAppExp.hs" Inplace 7 5 + , goldenTest "TErrorExp.hs" Inplace 6 15 + , goldenTest "TErrorExp.hs" Inplace 6 51 + , goldenTest "TQQExp.hs" Inplace 6 17 + , goldenTest "TQQExp.hs" Inplace 6 25 + , goldenTest "TQQExpError.hs" Inplace 6 13 + , goldenTest "TQQExpError.hs" Inplace 6 22 + , testGroup "Pattern Splices" + [ goldenTest "TSimplePat.hs" Inplace 6 3 + , goldenTest "TSimplePat.hs" Inplace 6 22 + , goldenTest "TSimplePat.hs" Inplace 6 3 + , goldenTest "TSimplePat.hs" Inplace 6 22 + , goldenTest "TErrorPat.hs" Inplace 6 3 + , goldenTest "TErrorPat.hs" Inplace 6 18 + , goldenTest "TQQPat.hs" Inplace 6 3 + , goldenTest "TQQPat.hs" Inplace 6 11 + , goldenTest "TQQPatError.hs" Inplace 6 3 + , goldenTest "TQQPatError.hs" Inplace 6 11 + ] + , goldenTest "TSimpleType.hs" Inplace 5 12 + , goldenTest "TSimpleType.hs" Inplace 5 22 + , goldenTest "TTypeTypeError.hs" Inplace 7 12 + , goldenTest "TTypeTypeError.hs" Inplace 7 52 + , goldenTest "TQQType.hs" Inplace 8 19 + , goldenTest "TQQType.hs" Inplace 8 28 + , goldenTest "TQQTypeTypeError.hs" Inplace 8 19 + , goldenTest "TQQTypeTypeError.hs" Inplace 8 28 + , goldenTest "TSimpleDecl.hs" Inplace 8 1 + , goldenTest "TQQDecl.hs" Inplace 5 1 + , goldenTestWithEdit "TTypeKindError.hs" Inplace 7 9 + , goldenTestWithEdit "TDeclKindError.hs" Inplace 8 1 + ] + +goldenTest :: FilePath -> ExpandStyle -> Int -> Int -> TestTree +goldenTest input tc line col = + testCase (input <> " (golden)") $ do + runSession hlsCommand fullCaps spliceTestPath $ do + doc <- openDoc input "haskell" + _ <- waitForDiagnostics + actions <- getCodeActions doc $ pointRange line col + case find ((== Just (toExpandCmdTitle tc)) . codeActionTitle) actions of + Just (CACodeAction CodeAction {_command = Just c}) -> do + executeCommand c + _resp :: ApplyWorkspaceEditRequest <- skipManyTill anyMessage message + edited <- documentContents doc + let expected_name = spliceTestPath 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 @?= expected + _ -> liftIO $ assertFailure "No CodeAction detected" + +goldenTestWithEdit :: FilePath -> ExpandStyle -> Int -> Int -> TestTree +goldenTestWithEdit input tc line col = + testCase (input <> " (golden)") $ do + runSession hlsCommand fullCaps spliceTestPath $ do + doc <- openDoc input "haskell" + orig <- documentContents doc + let lns = T.lines orig + theRange = + Range + { _start = Position 0 0 + , _end = Position (length lns + 1) 1 + } + liftIO $ sleep 3 + alt <- liftIO $ T.readFile (spliceTestPath input <.> "error") + void $ applyEdit doc $ TextEdit theRange alt + changeDoc doc [TextDocumentContentChangeEvent (Just theRange) Nothing alt] + void waitForDiagnostics + actions <- getCodeActions doc $ pointRange line col + case find ((== Just (toExpandCmdTitle tc)) . codeActionTitle) actions of + Just (CACodeAction CodeAction {_command = Just c}) -> do + executeCommand c + _resp :: ApplyWorkspaceEditRequest <- skipManyTill anyMessage message + edited <- documentContents doc + let expected_name = spliceTestPath 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 @?= expected + _ -> liftIO $ assertFailure "No CodeAction detected" + +spliceTestPath :: FilePath +spliceTestPath = "test/testdata/splice" + +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 :: CAResult -> Maybe Text +codeActionTitle CACommand {} = Nothing +codeActionTitle (CACodeAction (CodeAction title _ _ _ _)) = Just title diff --git a/test/testdata/splice/.gitignore b/test/testdata/splice/.gitignore new file mode 100644 index 00000000000..229336d2f3d --- /dev/null +++ b/test/testdata/splice/.gitignore @@ -0,0 +1 @@ +!hie.yaml diff --git a/test/testdata/splice/QQ.hs b/test/testdata/splice/QQ.hs new file mode 100644 index 00000000000..bf5efeb1b54 --- /dev/null +++ b/test/testdata/splice/QQ.hs @@ -0,0 +1,28 @@ +{-# LANGUAGE TemplateHaskell #-} +module QQ (str) where + +import Language.Haskell.TH + ( mkName, + stringL, + litP, + clause, + litE, + normalB, + funD, + sigD, + litT, + strTyLit ) +import Language.Haskell.TH.Quote (QuasiQuoter (..)) + +str :: QuasiQuoter +str = + QuasiQuoter + { quoteExp = litE . stringL + , quotePat = litP . stringL + , quoteType = litT . strTyLit + , quoteDec = \name -> + sequence + [ sigD (mkName name) [t|String|] + , funD (mkName name) [clause [] (normalB $ litE $ stringL name) []] + ] + } diff --git a/test/testdata/splice/TDeclKindError.hs b/test/testdata/splice/TDeclKindError.hs new file mode 100644 index 00000000000..027d4f83dd9 --- /dev/null +++ b/test/testdata/splice/TDeclKindError.hs @@ -0,0 +1,15 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE QuasiQuotes #-} +module TSimpleDecl where +import Language.Haskell.TH ( mkName, clause, normalB, funD, sigD ) + +-- Foo +-- Bar +$(sequence + [sigD (mkName "foo") [t|Int|] + ,funD (mkName "foo") [clause [] (normalB [|42|]) []] + ] + ) +-- Bar +-- ee +-- dddd diff --git a/test/testdata/splice/TDeclKindError.hs.error b/test/testdata/splice/TDeclKindError.hs.error new file mode 100644 index 00000000000..e21e057ed11 --- /dev/null +++ b/test/testdata/splice/TDeclKindError.hs.error @@ -0,0 +1,16 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE QuasiQuotes #-} +module TSimpleDecl where +import Language.Haskell.TH ( mkName, clause, normalB, funD, sigD ) + +-- Foo +-- Bar +$(sequence + [sigD (mkName "foo") [t|Int|] + ,funD (mkName "foo") [clause [] (normalB [|42|]) []] + ,sigD (mkName "bar") [t|Int|] + ] + ) +-- Bar +-- ee +-- dddd diff --git a/test/testdata/splice/TDeclKindError.hs.expected b/test/testdata/splice/TDeclKindError.hs.expected new file mode 100644 index 00000000000..b1f0250b419 --- /dev/null +++ b/test/testdata/splice/TDeclKindError.hs.expected @@ -0,0 +1,13 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE QuasiQuotes #-} +module TSimpleDecl where +import Language.Haskell.TH ( mkName, clause, normalB, funD, sigD ) + +-- Foo +-- Bar +foo :: Int +foo = 42 +bar :: Int +-- Bar +-- ee +-- dddd diff --git a/test/testdata/splice/TErrorExp.hs b/test/testdata/splice/TErrorExp.hs new file mode 100644 index 00000000000..fb696dc2dd5 --- /dev/null +++ b/test/testdata/splice/TErrorExp.hs @@ -0,0 +1,6 @@ +{-# LANGUAGE TemplateHaskell #-} +module TErrorExp where +import Language.Haskell.TH ( tupE, litE, integerL ) + +main :: IO () +main = return $(tupE [litE $ integerL 42, tupE []]) diff --git a/test/testdata/splice/TErrorExp.hs.expected b/test/testdata/splice/TErrorExp.hs.expected new file mode 100644 index 00000000000..420d9834ea4 --- /dev/null +++ b/test/testdata/splice/TErrorExp.hs.expected @@ -0,0 +1,6 @@ +{-# LANGUAGE TemplateHaskell #-} +module TErrorExp where +import Language.Haskell.TH ( tupE, litE, integerL ) + +main :: IO () +main = return (42, ()) diff --git a/test/testdata/splice/TErrorPat.hs b/test/testdata/splice/TErrorPat.hs new file mode 100644 index 00000000000..87f3d2c9cb5 --- /dev/null +++ b/test/testdata/splice/TErrorPat.hs @@ -0,0 +1,6 @@ +{-# LANGUAGE TemplateHaskell #-} +module TErrorPat where +import Language.Haskell.TH ( conP ) + +f :: () -> () +f $(conP 'True []) = x diff --git a/test/testdata/splice/TErrorPat.hs.expected b/test/testdata/splice/TErrorPat.hs.expected new file mode 100644 index 00000000000..184c9bd9ebd --- /dev/null +++ b/test/testdata/splice/TErrorPat.hs.expected @@ -0,0 +1,6 @@ +{-# LANGUAGE TemplateHaskell #-} +module TErrorPat where +import Language.Haskell.TH ( conP ) + +f :: () -> () +f True = x diff --git a/test/testdata/splice/TQQDecl.hs b/test/testdata/splice/TQQDecl.hs new file mode 100644 index 00000000000..90a05ce7d3c --- /dev/null +++ b/test/testdata/splice/TQQDecl.hs @@ -0,0 +1,5 @@ +{-# LANGUAGE QuasiQuotes #-} +module TQQDecl where +import QQ (str) + +[str|foo|] diff --git a/test/testdata/splice/TQQDecl.hs.expected b/test/testdata/splice/TQQDecl.hs.expected new file mode 100644 index 00000000000..781f23e12d8 --- /dev/null +++ b/test/testdata/splice/TQQDecl.hs.expected @@ -0,0 +1,6 @@ +{-# LANGUAGE QuasiQuotes #-} +module TQQDecl where +import QQ (str) + +foo :: String +foo = "foo" diff --git a/test/testdata/splice/TQQExp.hs b/test/testdata/splice/TQQExp.hs new file mode 100644 index 00000000000..b600df586ad --- /dev/null +++ b/test/testdata/splice/TQQExp.hs @@ -0,0 +1,6 @@ +{-# LANGUAGE QuasiQuotes #-} +module TQQExp where +import QQ + +main :: IO () +main = putStrLn [str|str|] diff --git a/test/testdata/splice/TQQExp.hs.expected b/test/testdata/splice/TQQExp.hs.expected new file mode 100644 index 00000000000..26f11695137 --- /dev/null +++ b/test/testdata/splice/TQQExp.hs.expected @@ -0,0 +1,6 @@ +{-# LANGUAGE QuasiQuotes #-} +module TQQExp where +import QQ + +main :: IO () +main = putStrLn "str" diff --git a/test/testdata/splice/TQQExpError.hs b/test/testdata/splice/TQQExpError.hs new file mode 100644 index 00000000000..56897837dac --- /dev/null +++ b/test/testdata/splice/TQQExpError.hs @@ -0,0 +1,6 @@ +{-# LANGUAGE QuasiQuotes #-} +module TQQExpError where +import QQ + +main :: IO () +main = pure [str|str|] diff --git a/test/testdata/splice/TQQExpError.hs.expected b/test/testdata/splice/TQQExpError.hs.expected new file mode 100644 index 00000000000..16c7678d0d5 --- /dev/null +++ b/test/testdata/splice/TQQExpError.hs.expected @@ -0,0 +1,6 @@ +{-# LANGUAGE QuasiQuotes #-} +module TQQExpError where +import QQ + +main :: IO () +main = pure "str" diff --git a/test/testdata/splice/TQQPat.hs b/test/testdata/splice/TQQPat.hs new file mode 100644 index 00000000000..e1ada41287d --- /dev/null +++ b/test/testdata/splice/TQQPat.hs @@ -0,0 +1,7 @@ +{-# LANGUAGE QuasiQuotes #-} +module TQQPat where +import QQ + +f :: String -> IO () +f [str|str|] = putStrLn "is str" +f _ = putStrLn " not str" diff --git a/test/testdata/splice/TQQPat.hs.expected b/test/testdata/splice/TQQPat.hs.expected new file mode 100644 index 00000000000..eb995240507 --- /dev/null +++ b/test/testdata/splice/TQQPat.hs.expected @@ -0,0 +1,7 @@ +{-# LANGUAGE QuasiQuotes #-} +module TQQPat where +import QQ + +f :: String -> IO () +f "str" = putStrLn "is str" +f _ = putStrLn " not str" diff --git a/test/testdata/splice/TQQPatError.hs b/test/testdata/splice/TQQPatError.hs new file mode 100644 index 00000000000..d89141a8754 --- /dev/null +++ b/test/testdata/splice/TQQPatError.hs @@ -0,0 +1,7 @@ +{-# LANGUAGE QuasiQuotes #-} +module TQQPatError where +import QQ + +f :: () -> IO () +f [str|str|] = putStrLn "is str" +f _ = putStrLn " not str" diff --git a/test/testdata/splice/TQQPatError.hs.expected b/test/testdata/splice/TQQPatError.hs.expected new file mode 100644 index 00000000000..0f928feab74 --- /dev/null +++ b/test/testdata/splice/TQQPatError.hs.expected @@ -0,0 +1,7 @@ +{-# LANGUAGE QuasiQuotes #-} +module TQQPatError where +import QQ + +f :: () -> IO () +f "str" = putStrLn "is str" +f _ = putStrLn " not str" diff --git a/test/testdata/splice/TQQType.hs b/test/testdata/splice/TQQType.hs new file mode 100644 index 00000000000..2c670793e23 --- /dev/null +++ b/test/testdata/splice/TQQType.hs @@ -0,0 +1,9 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE QuasiQuotes #-} +module TQQType where +import Language.Haskell.TH ( appT, numTyLit, litT, conT ) +import Data.Proxy ( Proxy(..) ) +import QQ + +main :: IO (Proxy [str|str|]) +main = return Proxy diff --git a/test/testdata/splice/TQQType.hs.expected b/test/testdata/splice/TQQType.hs.expected new file mode 100644 index 00000000000..f93798e01e5 --- /dev/null +++ b/test/testdata/splice/TQQType.hs.expected @@ -0,0 +1,9 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE QuasiQuotes #-} +module TQQType where +import Language.Haskell.TH ( appT, numTyLit, litT, conT ) +import Data.Proxy ( Proxy(..) ) +import QQ + +main :: IO (Proxy "str") +main = return Proxy diff --git a/test/testdata/splice/TQQTypeTypeError.hs b/test/testdata/splice/TQQTypeTypeError.hs new file mode 100644 index 00000000000..3f644a32889 --- /dev/null +++ b/test/testdata/splice/TQQTypeTypeError.hs @@ -0,0 +1,9 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE QuasiQuotes #-} +module TQQTypeTypeError where +import Language.Haskell.TH ( appT, numTyLit, litT, conT ) +import Data.Proxy ( Proxy(..) ) +import QQ + +main :: IO (Proxy [str|str|]) +main = return () diff --git a/test/testdata/splice/TQQTypeTypeError.hs.expected b/test/testdata/splice/TQQTypeTypeError.hs.expected new file mode 100644 index 00000000000..70e37e77018 --- /dev/null +++ b/test/testdata/splice/TQQTypeTypeError.hs.expected @@ -0,0 +1,9 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE QuasiQuotes #-} +module TQQTypeTypeError where +import Language.Haskell.TH ( appT, numTyLit, litT, conT ) +import Data.Proxy ( Proxy(..) ) +import QQ + +main :: IO (Proxy "str") +main = return () diff --git a/test/testdata/splice/TSimpleDecl.hs b/test/testdata/splice/TSimpleDecl.hs new file mode 100644 index 00000000000..027d4f83dd9 --- /dev/null +++ b/test/testdata/splice/TSimpleDecl.hs @@ -0,0 +1,15 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE QuasiQuotes #-} +module TSimpleDecl where +import Language.Haskell.TH ( mkName, clause, normalB, funD, sigD ) + +-- Foo +-- Bar +$(sequence + [sigD (mkName "foo") [t|Int|] + ,funD (mkName "foo") [clause [] (normalB [|42|]) []] + ] + ) +-- Bar +-- ee +-- dddd diff --git a/test/testdata/splice/TSimpleDecl.hs.expected b/test/testdata/splice/TSimpleDecl.hs.expected new file mode 100644 index 00000000000..90c2bf1b09d --- /dev/null +++ b/test/testdata/splice/TSimpleDecl.hs.expected @@ -0,0 +1,12 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE QuasiQuotes #-} +module TSimpleDecl where +import Language.Haskell.TH ( mkName, clause, normalB, funD, sigD ) + +-- Foo +-- Bar +foo :: Int +foo = 42 +-- Bar +-- ee +-- dddd diff --git a/test/testdata/splice/TSimpleExp.hs b/test/testdata/splice/TSimpleExp.hs new file mode 100644 index 00000000000..7f5db568ac4 --- /dev/null +++ b/test/testdata/splice/TSimpleExp.hs @@ -0,0 +1,6 @@ +{-# LANGUAGE TemplateHaskell #-} +module TSimpleExp where +import Language.Haskell.TH ( tupE, litE, integerL ) + +main :: IO () +main = return $(tupE []) diff --git a/test/testdata/splice/TSimpleExp.hs.expected b/test/testdata/splice/TSimpleExp.hs.expected new file mode 100644 index 00000000000..fb8967b5049 --- /dev/null +++ b/test/testdata/splice/TSimpleExp.hs.expected @@ -0,0 +1,6 @@ +{-# LANGUAGE TemplateHaskell #-} +module TSimpleExp where +import Language.Haskell.TH ( tupE, litE, integerL ) + +main :: IO () +main = return () diff --git a/test/testdata/splice/TSimplePat.hs b/test/testdata/splice/TSimplePat.hs new file mode 100644 index 00000000000..ee6f1d3ed3f --- /dev/null +++ b/test/testdata/splice/TSimplePat.hs @@ -0,0 +1,6 @@ +{-# LANGUAGE TemplateHaskell #-} +module TSimplePat where +import Language.Haskell.TH ( varP, mkName ) + +f :: x -> x +f $(varP $ mkName "x") = x diff --git a/test/testdata/splice/TSimplePat.hs.expected b/test/testdata/splice/TSimplePat.hs.expected new file mode 100644 index 00000000000..82c4891d3b0 --- /dev/null +++ b/test/testdata/splice/TSimplePat.hs.expected @@ -0,0 +1,6 @@ +{-# LANGUAGE TemplateHaskell #-} +module TSimplePat where +import Language.Haskell.TH ( varP, mkName ) + +f :: x -> x +f x = x diff --git a/test/testdata/splice/TSimpleType.hs b/test/testdata/splice/TSimpleType.hs new file mode 100644 index 00000000000..55b5c59d052 --- /dev/null +++ b/test/testdata/splice/TSimpleType.hs @@ -0,0 +1,6 @@ +{-# LANGUAGE TemplateHaskell #-} +module TSimpleType where +import Language.Haskell.TH ( tupleT ) + +main :: IO $(tupleT 0) +main = return () diff --git a/test/testdata/splice/TSimpleType.hs.expected b/test/testdata/splice/TSimpleType.hs.expected new file mode 100644 index 00000000000..8975b4f926a --- /dev/null +++ b/test/testdata/splice/TSimpleType.hs.expected @@ -0,0 +1,6 @@ +{-# LANGUAGE TemplateHaskell #-} +module TSimpleType where +import Language.Haskell.TH ( tupleT ) + +main :: IO () +main = return () diff --git a/test/testdata/splice/TTypeAppExp.hs b/test/testdata/splice/TTypeAppExp.hs new file mode 100644 index 00000000000..0cc071a08db --- /dev/null +++ b/test/testdata/splice/TTypeAppExp.hs @@ -0,0 +1,7 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} +module TTypeAppExp where +import Data.Proxy + +f :: Proxy Int +f = $([|Proxy @Int|]) diff --git a/test/testdata/splice/TTypeAppExp.hs.expected b/test/testdata/splice/TTypeAppExp.hs.expected new file mode 100644 index 00000000000..0dc0e40f2ad --- /dev/null +++ b/test/testdata/splice/TTypeAppExp.hs.expected @@ -0,0 +1,7 @@ +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE TypeApplications #-} +module TTypeAppExp where +import Data.Proxy + +f :: Proxy Int +f = (Proxy @Int) diff --git a/test/testdata/splice/TTypeKindError.hs b/test/testdata/splice/TTypeKindError.hs new file mode 100644 index 00000000000..c14dc0e68c7 --- /dev/null +++ b/test/testdata/splice/TTypeKindError.hs @@ -0,0 +1,8 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TemplateHaskell #-} +module TTypeKindError where +import Language.Haskell.TH ( numTyLit, litT ) +import Data.Proxy ( Proxy ) + +main :: IO () +main = return () diff --git a/test/testdata/splice/TTypeKindError.hs.error b/test/testdata/splice/TTypeKindError.hs.error new file mode 100644 index 00000000000..58631e8464a --- /dev/null +++ b/test/testdata/splice/TTypeKindError.hs.error @@ -0,0 +1,8 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TemplateHaskell #-} +module TTypeKindError where +import Language.Haskell.TH ( numTyLit, litT ) +import Data.Proxy ( Proxy ) + +main :: $(litT (numTyLit 42)) +main = return () diff --git a/test/testdata/splice/TTypeKindError.hs.expected b/test/testdata/splice/TTypeKindError.hs.expected new file mode 100644 index 00000000000..ef04a42611d --- /dev/null +++ b/test/testdata/splice/TTypeKindError.hs.expected @@ -0,0 +1,8 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TemplateHaskell #-} +module TTypeKindError where +import Language.Haskell.TH ( numTyLit, litT ) +import Data.Proxy ( Proxy ) + +main :: (42) +main = return () diff --git a/test/testdata/splice/TTypeTypeError.hs b/test/testdata/splice/TTypeTypeError.hs new file mode 100644 index 00000000000..37a8b3c9315 --- /dev/null +++ b/test/testdata/splice/TTypeTypeError.hs @@ -0,0 +1,8 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TemplateHaskell #-} +module TTypeTypeError where +import Language.Haskell.TH ( appT, numTyLit, litT, conT ) +import Data.Proxy ( Proxy ) + +main :: IO $(conT ''Proxy `appT` litT (numTyLit 42)) +main = return () diff --git a/test/testdata/splice/TTypeTypeError.hs.expected b/test/testdata/splice/TTypeTypeError.hs.expected new file mode 100644 index 00000000000..f19e495e6d3 --- /dev/null +++ b/test/testdata/splice/TTypeTypeError.hs.expected @@ -0,0 +1,8 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE TemplateHaskell #-} +module TTypeTypeError where +import Language.Haskell.TH ( appT, numTyLit, litT, conT ) +import Data.Proxy ( Proxy ) + +main :: IO (Proxy 42) +main = return () diff --git a/test/testdata/splice/hie.yaml b/test/testdata/splice/hie.yaml new file mode 100644 index 00000000000..39bd673f43b --- /dev/null +++ b/test/testdata/splice/hie.yaml @@ -0,0 +1,21 @@ +cradle: + direct: + arguments: + - QQ.hs + - TQQExpError.hs + - TSimpleExp.hs + - TTypeAppExp.hs + - TDeclKindError.hs + - TQQPat.hs + - TSimplePat.hs + - TErrorExp.hs + - TQQPatError.hs + - TSimpleType.hs + - TErrorPat.hs + - TQQType.hs + - TTypeKindError.hs + - TQQDecl.hs + - TQQTypeTypeError.hs + - TTypeTypeError.hs + - TQQExp.hs + - TSimpleDecl.hs