haskell-tools-refactor 0.3.0.1 → 0.4.0.0
raw patch · 18 files changed
+1246/−325 lines, 18 filesdep +tastydep +tasty-hunitdep −HUnitdep ~haskell-tools-astdep ~haskell-tools-backend-ghcdep ~haskell-tools-prettyprintPVP ok
version bump matches the API change (PVP)
Dependencies added: tasty, tasty-hunit
Dependencies removed: HUnit
Dependency ranges changed: haskell-tools-ast, haskell-tools-backend-ghc, haskell-tools-prettyprint, haskell-tools-refactor, haskell-tools-rewrite
API changes (from Hackage documentation)
- Language.Haskell.Tools.Refactor.ListOperations: replaceList :: [Ann e dom SrcTemplateStage] -> AnnListG e dom SrcTemplateStage -> AnnListG e dom SrcTemplateStage
- Language.Haskell.Tools.Refactor.Prepare: IsHsBoot :: IsBoot
- Language.Haskell.Tools.Refactor.Prepare: NormalHs :: IsBoot
- Language.Haskell.Tools.Refactor.Prepare: data IsBoot
- Language.Haskell.Tools.Refactor.Prepare: instance GHC.Classes.Eq Language.Haskell.Tools.Refactor.Prepare.IsBoot
- Language.Haskell.Tools.Refactor.Prepare: instance GHC.Classes.Ord Language.Haskell.Tools.Refactor.Prepare.IsBoot
- Language.Haskell.Tools.Refactor.Prepare: instance GHC.Show.Show Language.Haskell.Tools.Refactor.Prepare.IsBoot
+ Language.Haskell.Tools.Refactor: class SourceInfoTraversal (a :: * -> * -> *)
+ Language.Haskell.Tools.Refactor: data SrcTemplateStage :: *
+ Language.Haskell.Tools.Refactor: sourceInfoTraverse :: (SourceInfoTraversal a, Monad f) => SourceInfoTrf f st1 st2 -> a dom st1 -> f (a dom st2)
+ Language.Haskell.Tools.Refactor: sourceInfoTraverseDown :: (SourceInfoTraversal a, Monad f) => SourceInfoTrf f st1 st2 -> f () -> f () -> a dom st1 -> f (a dom st2)
+ Language.Haskell.Tools.Refactor: sourceInfoTraverseUp :: (SourceInfoTraversal a, Monad f) => SourceInfoTrf f st1 st2 -> f () -> f () -> a dom st1 -> f (a dom st2)
+ Language.Haskell.Tools.Refactor.BindingElem: createFixitySig :: BindingElem d => FixitySignature dom -> Ann d dom SrcTemplateStage
+ Language.Haskell.Tools.Refactor.BindingElem: fixitySig :: BindingElem d => Simple Partial (Ann d dom SrcTemplateStage) (FixitySignature dom)
+ Language.Haskell.Tools.Refactor.BindingElem: isFixitySig :: BindingElem d => Ann d dom SrcTemplateStage -> Bool
+ Language.Haskell.Tools.Refactor.GetModules: BenchmarkMC :: String -> String -> ModuleCollectionId
+ Language.Haskell.Tools.Refactor.GetModules: DirectoryMC :: FilePath -> ModuleCollectionId
+ Language.Haskell.Tools.Refactor.GetModules: ExecutableMC :: String -> String -> ModuleCollectionId
+ Language.Haskell.Tools.Refactor.GetModules: LibraryMC :: String -> ModuleCollectionId
+ Language.Haskell.Tools.Refactor.GetModules: ModuleCodeGenerated :: UnnamedModule IdDom -> ModSummary -> ModuleRecord
+ Language.Haskell.Tools.Refactor.GetModules: ModuleCollection :: ModuleCollectionId -> FilePath -> [FilePath] -> Map SourceFileKey ModuleRecord -> (DynFlags -> IO DynFlags) -> [ModuleCollectionId] -> ModuleCollection
+ Language.Haskell.Tools.Refactor.GetModules: ModuleNotLoaded :: Bool -> ModuleRecord
+ Language.Haskell.Tools.Refactor.GetModules: ModuleParsed :: UnnamedModule (Dom RdrName) -> ModSummary -> ModuleRecord
+ Language.Haskell.Tools.Refactor.GetModules: ModuleRenamed :: UnnamedModule (Dom Name) -> ModSummary -> ModuleRecord
+ Language.Haskell.Tools.Refactor.GetModules: ModuleTypeChecked :: UnnamedModule IdDom -> ModSummary -> ModuleRecord
+ Language.Haskell.Tools.Refactor.GetModules: TestSuiteMC :: String -> String -> ModuleCollectionId
+ Language.Haskell.Tools.Refactor.GetModules: [_mcDependencies] :: ModuleCollection -> [ModuleCollectionId]
+ Language.Haskell.Tools.Refactor.GetModules: [_mcFlagSetup] :: ModuleCollection -> DynFlags -> IO DynFlags
+ Language.Haskell.Tools.Refactor.GetModules: [_mcId] :: ModuleCollection -> ModuleCollectionId
+ Language.Haskell.Tools.Refactor.GetModules: [_mcModules] :: ModuleCollection -> Map SourceFileKey ModuleRecord
+ Language.Haskell.Tools.Refactor.GetModules: [_mcRoot] :: ModuleCollection -> FilePath
+ Language.Haskell.Tools.Refactor.GetModules: [_mcSourceDirs] :: ModuleCollection -> [FilePath]
+ Language.Haskell.Tools.Refactor.GetModules: [_modRecMS] :: ModuleRecord -> ModSummary
+ Language.Haskell.Tools.Refactor.GetModules: [_parsedRecModule] :: ModuleRecord -> UnnamedModule (Dom RdrName)
+ Language.Haskell.Tools.Refactor.GetModules: [_recModuleWillNeedCode] :: ModuleRecord -> Bool
+ Language.Haskell.Tools.Refactor.GetModules: [_renamedRecModule] :: ModuleRecord -> UnnamedModule (Dom Name)
+ Language.Haskell.Tools.Refactor.GetModules: [_typedRecModule] :: ModuleRecord -> UnnamedModule IdDom
+ Language.Haskell.Tools.Refactor.GetModules: applyDependencies :: [ModuleCollection] -> [ModuleCollectionId] -> DynFlags -> DynFlags
+ Language.Haskell.Tools.Refactor.GetModules: class ToModuleCollection t
+ Language.Haskell.Tools.Refactor.GetModules: codeGeneratedFor :: SourceFileKey -> [ModuleCollection] -> [ModuleCollection]
+ Language.Haskell.Tools.Refactor.GetModules: compileInContext :: ModuleCollection -> [ModuleCollection] -> DynFlags -> IO DynFlags
+ Language.Haskell.Tools.Refactor.GetModules: data ModuleCollection
+ Language.Haskell.Tools.Refactor.GetModules: data ModuleCollectionId
+ Language.Haskell.Tools.Refactor.GetModules: data ModuleRecord
+ Language.Haskell.Tools.Refactor.GetModules: dependencyToPkgFlag :: [ModuleCollection] -> ModuleCollectionId -> Maybe (PackageFlag)
+ Language.Haskell.Tools.Refactor.GetModules: enableAllPackages :: [ModuleCollection] -> DynFlags -> DynFlags
+ Language.Haskell.Tools.Refactor.GetModules: flagsFromBuildInfo :: BuildInfo -> DynFlags -> IO DynFlags
+ Language.Haskell.Tools.Refactor.GetModules: getAllModules :: [FilePath] -> IO [ModuleCollection]
+ Language.Haskell.Tools.Refactor.GetModules: getBuildInfo :: ToModuleCollection t => t -> BuildInfo
+ Language.Haskell.Tools.Refactor.GetModules: getModuleNames :: ToModuleCollection t => t -> [ModuleName]
+ Language.Haskell.Tools.Refactor.GetModules: hasGeneratedCode :: SourceFileKey -> [ModuleCollection] -> Bool
+ Language.Haskell.Tools.Refactor.GetModules: instance GHC.Classes.Eq Language.Haskell.Tools.Refactor.GetModules.ModuleCollection
+ Language.Haskell.Tools.Refactor.GetModules: instance GHC.Classes.Eq Language.Haskell.Tools.Refactor.GetModules.ModuleCollectionId
+ Language.Haskell.Tools.Refactor.GetModules: instance GHC.Classes.Ord Language.Haskell.Tools.Refactor.GetModules.ModuleCollectionId
+ Language.Haskell.Tools.Refactor.GetModules: instance GHC.Show.Show Language.Haskell.Tools.Refactor.GetModules.ModuleCollection
+ Language.Haskell.Tools.Refactor.GetModules: instance GHC.Show.Show Language.Haskell.Tools.Refactor.GetModules.ModuleCollectionId
+ Language.Haskell.Tools.Refactor.GetModules: instance GHC.Show.Show Language.Haskell.Tools.Refactor.GetModules.ModuleRecord
+ Language.Haskell.Tools.Refactor.GetModules: instance Language.Haskell.Tools.Refactor.GetModules.ToModuleCollection Distribution.PackageDescription.Benchmark
+ Language.Haskell.Tools.Refactor.GetModules: instance Language.Haskell.Tools.Refactor.GetModules.ToModuleCollection Distribution.PackageDescription.Executable
+ Language.Haskell.Tools.Refactor.GetModules: instance Language.Haskell.Tools.Refactor.GetModules.ToModuleCollection Distribution.PackageDescription.Library
+ Language.Haskell.Tools.Refactor.GetModules: instance Language.Haskell.Tools.Refactor.GetModules.ToModuleCollection Distribution.PackageDescription.TestSuite
+ Language.Haskell.Tools.Refactor.GetModules: isAlreadyLoaded :: SourceFileKey -> [ModuleCollection] -> Bool
+ Language.Haskell.Tools.Refactor.GetModules: lookupModInSCs :: SourceFileKey -> [ModuleCollection] -> Maybe (SourceFileKey, ModuleRecord)
+ Language.Haskell.Tools.Refactor.GetModules: lookupModuleColl :: String -> [ModuleCollection] -> Maybe (ModuleCollection)
+ Language.Haskell.Tools.Refactor.GetModules: mcDependencies :: Lens ModuleCollection ModuleCollection [ModuleCollectionId] [ModuleCollectionId]
+ Language.Haskell.Tools.Refactor.GetModules: mcFlagSetup :: Lens ModuleCollection ModuleCollection (DynFlags -> IO DynFlags) (DynFlags -> IO DynFlags)
+ Language.Haskell.Tools.Refactor.GetModules: mcId :: Lens ModuleCollection ModuleCollection ModuleCollectionId ModuleCollectionId
+ Language.Haskell.Tools.Refactor.GetModules: mcModules :: Lens ModuleCollection ModuleCollection (Map SourceFileKey ModuleRecord) (Map SourceFileKey ModuleRecord)
+ Language.Haskell.Tools.Refactor.GetModules: mcRoot :: Lens ModuleCollection ModuleCollection FilePath FilePath
+ Language.Haskell.Tools.Refactor.GetModules: mcSourceDirs :: Lens ModuleCollection ModuleCollection [FilePath] [FilePath]
+ Language.Haskell.Tools.Refactor.GetModules: mkModuleCollKey :: ToModuleCollection t => PackageName -> t -> ModuleCollectionId
+ Language.Haskell.Tools.Refactor.GetModules: modRecMS :: Partial ModuleRecord ModuleRecord ModSummary ModSummary
+ Language.Haskell.Tools.Refactor.GetModules: moduleCollectionIdString :: ModuleCollectionId -> String
+ Language.Haskell.Tools.Refactor.GetModules: moduleCollectionPkgId :: ModuleCollectionId -> Maybe String
+ Language.Haskell.Tools.Refactor.GetModules: needsGeneratedCode :: SourceFileKey -> [ModuleCollection] -> Bool
+ Language.Haskell.Tools.Refactor.GetModules: orderMCs :: [ModuleCollection] -> [ModuleCollection]
+ Language.Haskell.Tools.Refactor.GetModules: parsedRecModule :: Partial ModuleRecord ModuleRecord (UnnamedModule (Dom RdrName)) (UnnamedModule (Dom RdrName))
+ Language.Haskell.Tools.Refactor.GetModules: recModuleWillNeedCode :: Partial ModuleRecord ModuleRecord Bool Bool
+ Language.Haskell.Tools.Refactor.GetModules: removeModule :: String -> [ModuleCollection] -> [ModuleCollection]
+ Language.Haskell.Tools.Refactor.GetModules: renamedRecModule :: Partial ModuleRecord ModuleRecord (UnnamedModule (Dom Name)) (UnnamedModule (Dom Name))
+ Language.Haskell.Tools.Refactor.GetModules: typedRecModule :: Partial ModuleRecord ModuleRecord (UnnamedModule IdDom) (UnnamedModule IdDom)
+ Language.Haskell.Tools.Refactor.Perform: InlineBinding :: RealSrcSpan -> RefactorCommand
+ Language.Haskell.Tools.Refactor.Predefined.InlineBinding: inlineBinding :: forall dom. InlineBindingDomain dom => RealSrcSpan -> Refactoring dom
+ Language.Haskell.Tools.Refactor.Predefined.InlineBinding: type InlineBindingDomain dom = (HasNameInfo dom, HasDefiningInfo dom, HasScopeInfo dom, HasModuleInfo dom)
+ Language.Haskell.Tools.Refactor.Prepare: correctRefactorSpan :: UnnamedModule dom -> RealSrcSpan -> RealSrcSpan
+ Language.Haskell.Tools.Refactor.Prepare: deregisterDirs :: [FilePath] -> Ghc ()
+ Language.Haskell.Tools.Refactor.Prepare: forceAsmGen :: ModSummary -> ModSummary
+ Language.Haskell.Tools.Refactor.Prepare: forceCodeGen :: ModSummary -> ModSummary
+ Language.Haskell.Tools.Refactor.Prepare: getModSumOrig :: ModSummary -> FilePath
+ Language.Haskell.Tools.Refactor.Prepare: getSourceDir :: ModSummary -> IO FilePath
+ Language.Haskell.Tools.Refactor.Prepare: initGhcFlags' :: Bool -> Ghc ()
+ Language.Haskell.Tools.Refactor.Prepare: initGhcFlagsForTest :: Ghc ()
+ Language.Haskell.Tools.Refactor.Prepare: modSumNormalizeFlags :: ModSummary -> ModSummary
+ Language.Haskell.Tools.Refactor.Prepare: normalizeFlags :: DynFlags -> DynFlags
+ Language.Haskell.Tools.Refactor.Prepare: withAlteredDynFlags :: GhcMonad m => (DynFlags -> m DynFlags) -> m a -> m a
+ Language.Haskell.Tools.Refactor.RefactorBase: IsHsBoot :: IsBoot
+ Language.Haskell.Tools.Refactor.RefactorBase: ModuleCreated :: String -> UnnamedModule dom -> SourceFileKey -> RefactorChange dom
+ Language.Haskell.Tools.Refactor.RefactorBase: NormalHs :: IsBoot
+ Language.Haskell.Tools.Refactor.RefactorBase: SourceFileKey :: IsBoot -> String -> SourceFileKey
+ Language.Haskell.Tools.Refactor.RefactorBase: [_sfkIsBoot] :: SourceFileKey -> IsBoot
+ Language.Haskell.Tools.Refactor.RefactorBase: [_sfkModuleName] :: SourceFileKey -> String
+ Language.Haskell.Tools.Refactor.RefactorBase: [createdModuleContent] :: RefactorChange dom -> UnnamedModule dom
+ Language.Haskell.Tools.Refactor.RefactorBase: [createdModuleName] :: RefactorChange dom -> String
+ Language.Haskell.Tools.Refactor.RefactorBase: [sameLocation] :: RefactorChange dom -> SourceFileKey
+ Language.Haskell.Tools.Refactor.RefactorBase: data IsBoot
+ Language.Haskell.Tools.Refactor.RefactorBase: data SourceFileKey
+ Language.Haskell.Tools.Refactor.RefactorBase: instance (GHC.Base.Monad m, DynFlags.HasDynFlags m) => DynFlags.HasDynFlags (Control.Monad.Trans.State.Lazy.StateT s m)
+ Language.Haskell.Tools.Refactor.RefactorBase: instance Exception.ExceptionMonad m => Exception.ExceptionMonad (Control.Monad.Trans.State.Lazy.StateT s m)
+ Language.Haskell.Tools.Refactor.RefactorBase: instance GHC.Classes.Eq Language.Haskell.Tools.Refactor.RefactorBase.IsBoot
+ Language.Haskell.Tools.Refactor.RefactorBase: instance GHC.Classes.Eq Language.Haskell.Tools.Refactor.RefactorBase.SourceFileKey
+ Language.Haskell.Tools.Refactor.RefactorBase: instance GHC.Classes.Ord Language.Haskell.Tools.Refactor.RefactorBase.IsBoot
+ Language.Haskell.Tools.Refactor.RefactorBase: instance GHC.Classes.Ord Language.Haskell.Tools.Refactor.RefactorBase.SourceFileKey
+ Language.Haskell.Tools.Refactor.RefactorBase: instance GHC.Show.Show (Language.Haskell.Tools.Refactor.RefactorBase.RefactorChange dom)
+ Language.Haskell.Tools.Refactor.RefactorBase: instance GHC.Show.Show Language.Haskell.Tools.Refactor.RefactorBase.IsBoot
+ Language.Haskell.Tools.Refactor.RefactorBase: instance GHC.Show.Show Language.Haskell.Tools.Refactor.RefactorBase.SourceFileKey
+ Language.Haskell.Tools.Refactor.RefactorBase: instance GhcMonad.GhcMonad m => GhcMonad.GhcMonad (Control.Monad.Trans.State.Lazy.StateT s m)
+ Language.Haskell.Tools.Refactor.RefactorBase: sfkIsBoot :: Lens SourceFileKey SourceFileKey IsBoot IsBoot
+ Language.Haskell.Tools.Refactor.RefactorBase: sfkModuleName :: Lens SourceFileKey SourceFileKey String String
+ Language.Haskell.Tools.Refactor.Session: RefactorSessionState :: [ModuleCollection] -> RefactorSessionState
+ Language.Haskell.Tools.Refactor.Session: [__refSessMCs] :: RefactorSessionState -> [ModuleCollection]
+ Language.Haskell.Tools.Refactor.Session: _refSessMCs :: Lens RefactorSessionState RefactorSessionState [ModuleCollection] [ModuleCollection]
+ Language.Haskell.Tools.Refactor.Session: checkEvaluatedMods :: IsRefactSessionState st => (ModSummary -> IO a) -> [ModSummary] -> StateT st Ghc [a]
+ Language.Haskell.Tools.Refactor.Session: class IsRefactSessionState st
+ Language.Haskell.Tools.Refactor.Session: codeGenForModule :: (ModSummary -> IO a) -> [ModuleCollection] -> ModSummary -> Ghc a
+ Language.Haskell.Tools.Refactor.Session: data RefactorSessionState
+ Language.Haskell.Tools.Refactor.Session: getEvaluatedMods :: [ModSummary] -> Ghc [ModSummary]
+ Language.Haskell.Tools.Refactor.Session: getFileMods :: (GhcMonad m, IsRefactSessionState st) => FilePath -> StateT st m (Maybe (SourceFileKey, UnnamedModule IdDom), [(SourceFileKey, UnnamedModule IdDom)])
+ Language.Haskell.Tools.Refactor.Session: getModFromNode :: SummaryNode -> ModSummary
+ Language.Haskell.Tools.Refactor.Session: getMods :: (Monad m, IsRefactSessionState st) => Maybe SourceFileKey -> StateT st m (Maybe (SourceFileKey, UnnamedModule IdDom), [(SourceFileKey, UnnamedModule IdDom)])
+ Language.Haskell.Tools.Refactor.Session: getReachableModules :: IsRefactSessionState st => (ModSummary -> Bool) -> StateT st Ghc [ModSummary]
+ Language.Haskell.Tools.Refactor.Session: home_imps :: [(Maybe FastString, Located ModuleName)] -> [Located ModuleName]
+ Language.Haskell.Tools.Refactor.Session: hscSourceToIsBoot :: HscSource -> IsBoot
+ Language.Haskell.Tools.Refactor.Session: initSession :: IsRefactSessionState st => st
+ Language.Haskell.Tools.Refactor.Session: instance Language.Haskell.Tools.Refactor.Session.IsRefactSessionState Language.Haskell.Tools.Refactor.Session.RefactorSessionState
+ Language.Haskell.Tools.Refactor.Session: keyFromMS :: ModSummary -> SourceFileKey
+ Language.Haskell.Tools.Refactor.Session: loadPackagesFrom :: IsRefactSessionState st => (ModSummary -> IO a) -> [FilePath] -> StateT st Ghc ([a], [String])
+ Language.Haskell.Tools.Refactor.Session: modSumName :: ModSummary -> String
+ Language.Haskell.Tools.Refactor.Session: moduleGraphNodes :: Bool -> [ModSummary] -> (Graph SummaryNode, HscSource -> ModuleName -> Maybe SummaryNode)
+ Language.Haskell.Tools.Refactor.Session: ms_home_imps :: ModSummary -> [Located ModuleName]
+ Language.Haskell.Tools.Refactor.Session: ms_home_srcimps :: ModSummary -> [Located ModuleName]
+ Language.Haskell.Tools.Refactor.Session: refSessMCs :: IsRefactSessionState st => Simple Lens st [ModuleCollection]
+ Language.Haskell.Tools.Refactor.Session: reloadChangedModules :: IsRefactSessionState st => (ModSummary -> IO a) -> (ModSummary -> Bool) -> StateT st Ghc [a]
+ Language.Haskell.Tools.Refactor.Session: reloadModule :: IsRefactSessionState st => (ModSummary -> IO a) -> ModSummary -> StateT st Ghc a
+ Language.Haskell.Tools.Refactor.Session: summaryNodeKey :: SummaryNode -> Int
+ Language.Haskell.Tools.Refactor.Session: type NodeKey = (ModuleName, IsBoot)
+ Language.Haskell.Tools.Refactor.Session: type NodeMap a = Map NodeKey a
+ Language.Haskell.Tools.Refactor.Session: type SummaryNode = (ModSummary, Int, [Int])
- Language.Haskell.Tools.Refactor.GetModules: getModules :: FilePath -> IO [([FilePath], [String])]
+ Language.Haskell.Tools.Refactor.GetModules: getModules :: FilePath -> IO [ModuleCollection]
- Language.Haskell.Tools.Refactor.GetModules: modulesFromCabalFile :: FilePath -> IO [([FilePath], [String])]
+ Language.Haskell.Tools.Refactor.GetModules: modulesFromCabalFile :: FilePath -> FilePath -> IO [ModuleCollection]
- Language.Haskell.Tools.Refactor.Perform: analyzeCommand :: String -> String -> [String] -> RefactorCommand
+ Language.Haskell.Tools.Refactor.Perform: analyzeCommand :: String -> [String] -> RefactorCommand
- Language.Haskell.Tools.Refactor.Perform: readCommand :: String -> String -> RefactorCommand
+ Language.Haskell.Tools.Refactor.Perform: readCommand :: String -> RefactorCommand
- Language.Haskell.Tools.Refactor.Predefined.GenerateTypeSignature: type GenerateSignatureDomain dom = (HasModuleInfo dom, HasIdInfo dom, HasImportInfo dom)
+ Language.Haskell.Tools.Refactor.Predefined.GenerateTypeSignature: type GenerateSignatureDomain dom = (HasModuleInfo dom, HasIdInfo dom, HasImportInfo dom, HasScopeInfo dom)
- Language.Haskell.Tools.Refactor.Prepare: readSrcLoc :: String -> String -> RealSrcLoc
+ Language.Haskell.Tools.Refactor.Prepare: readSrcLoc :: String -> RealSrcLoc
- Language.Haskell.Tools.Refactor.Prepare: readSrcSpan :: String -> String -> RealSrcSpan
+ Language.Haskell.Tools.Refactor.Prepare: readSrcSpan :: String -> RealSrcSpan
- Language.Haskell.Tools.Refactor.Prepare: toBootFileName :: String -> String -> FilePath
+ Language.Haskell.Tools.Refactor.Prepare: toBootFileName :: FilePath -> String -> FilePath
- Language.Haskell.Tools.Refactor.Prepare: toFileName :: String -> String -> FilePath
+ Language.Haskell.Tools.Refactor.Prepare: toFileName :: FilePath -> String -> FilePath
- Language.Haskell.Tools.Refactor.Prepare: tryRefactor :: Refactoring IdDom -> String -> IO ()
+ Language.Haskell.Tools.Refactor.Prepare: tryRefactor :: (RealSrcSpan -> Refactoring IdDom) -> String -> String -> IO ()
- Language.Haskell.Tools.Refactor.RefactorBase: type ModuleDom dom = (String, UnnamedModule dom)
+ Language.Haskell.Tools.Refactor.RefactorBase: type ModuleDom dom = (SourceFileKey, UnnamedModule dom)
Files
- Language/Haskell/Tools/Refactor.hs +3/−1
- Language/Haskell/Tools/Refactor/BindingElem.hs +17/−0
- Language/Haskell/Tools/Refactor/GetModules.hs +330/−19
- Language/Haskell/Tools/Refactor/ListOperations.hs +15/−11
- Language/Haskell/Tools/Refactor/Perform.hs +18/−19
- Language/Haskell/Tools/Refactor/Predefined/DataToNewtype.hs +1/−1
- Language/Haskell/Tools/Refactor/Predefined/DollarApp.hs +1/−2
- Language/Haskell/Tools/Refactor/Predefined/ExtractBinding.hs +14/−8
- Language/Haskell/Tools/Refactor/Predefined/GenerateTypeSignature.hs +61/−19
- Language/Haskell/Tools/Refactor/Predefined/IfToGuards.hs +1/−1
- Language/Haskell/Tools/Refactor/Predefined/InlineBinding.hs +223/−0
- Language/Haskell/Tools/Refactor/Predefined/OrganizeImports.hs +1/−1
- Language/Haskell/Tools/Refactor/Predefined/RenameDefinition.hs +10/−3
- Language/Haskell/Tools/Refactor/Prepare.hs +92/−25
- Language/Haskell/Tools/Refactor/RefactorBase.hs +35/−3
- Language/Haskell/Tools/Refactor/Session.hs +234/−0
- haskell-tools-refactor.cabal +16/−12
- test/Main.hs +174/−200
Language/Haskell/Tools/Refactor.hs view
@@ -9,8 +9,9 @@ , module Language.Haskell.Tools.Refactor.Prepare , module Language.Haskell.Tools.Refactor.ListOperations , module Language.Haskell.Tools.Refactor.BindingElem + , module Language.Haskell.Tools.IndentationUtils , HasRange(..), annListElems, annList, annJust, annMaybe, isAnnNothing, Domain - , shortShowSpan + , shortShowSpan, SrcTemplateStage, SourceInfoTraversal(..) ) where -- Important: Haddock doesn't support the rename all exported modules and export them at once hack @@ -24,5 +25,6 @@ import Language.Haskell.Tools.Refactor.Prepare import Language.Haskell.Tools.Refactor.ListOperations import Language.Haskell.Tools.Refactor.BindingElem +import Language.Haskell.Tools.IndentationUtils import Language.Haskell.Tools.AST.Ann
Language/Haskell/Tools/Refactor/BindingElem.hs view
@@ -19,37 +19,54 @@ -- | Accesses a value or function definition in a local or top-level definition valBind :: Simple Partial (Ann d dom SrcTemplateStage) (ValueBind dom) + -- | Accesses a type signature definition in a local or top-level definition + fixitySig :: Simple Partial (Ann d dom SrcTemplateStage) (FixitySignature dom) + -- | Creates a new definition from a type signature createTypeSig :: TypeSignature dom -> Ann d dom SrcTemplateStage -- | Creates a new definition from a value or function definition createBinding :: ValueBind dom -> Ann d dom SrcTemplateStage + -- | Creates a new fixity signature + createFixitySig :: FixitySignature dom -> Ann d dom SrcTemplateStage + -- | Checks if a given definition is a type signature isTypeSig :: Ann d dom SrcTemplateStage -> Bool -- | Checks if a given definition is a function or value binding isBinding :: Ann d dom SrcTemplateStage -> Bool + + -- | Checks if a given definition is a fixity signature + isFixitySig :: Ann d dom SrcTemplateStage -> Bool instance BindingElem UDecl where sigBind = declTypeSig valBind = declValBind + fixitySig = declFixity createTypeSig = mkTypeSigDecl createBinding = mkValueBinding + createFixitySig = mkFixityDecl isTypeSig TypeSigDecl {} = True isTypeSig _ = False isBinding ValueBinding {} = True isBinding _ = False + isFixitySig FixityDecl {} = True + isFixitySig _ = False instance BindingElem ULocalBind where sigBind = localSig valBind = localVal + fixitySig = localFixity createTypeSig = mkLocalTypeSig createBinding = mkLocalValBind + createFixitySig = mkLocalFixity isTypeSig LocalTypeSig {} = True isTypeSig _ = False isBinding LocalValBind {} = True isBinding _ = False + isFixitySig LocalFixity {} = True + isFixitySig _ = False getValBindInList :: (BindingElem d) => RealSrcSpan -> AnnListG d dom SrcTemplateStage -> Maybe (ValueBind dom) getValBindInList sp ls = case ls ^? valBindsInList & filtered (isInside sp) of
Language/Haskell/Tools/Refactor/GetModules.hs view
@@ -1,37 +1,154 @@+{-# LANGUAGE TupleSections + , NamedFieldPuns + , LambdaCase + , TemplateHaskell + , FlexibleContexts + #-} module Language.Haskell.Tools.Refactor.GetModules where -import Data.List (intersperse, find) +import Control.Reference +import Data.Function (on) +import Data.List (intersperse, find, sortBy) +import qualified Data.Map as Map +import Data.Maybe +import Distribution.Package (Dependency(..), PackageName(..), pkgName) import Distribution.Verbosity (silent) import Distribution.ModuleName (components) +import Distribution.ModuleName import Distribution.PackageDescription import Distribution.PackageDescription.Configuration import Distribution.PackageDescription.Parse import System.FilePath.Posix import System.Directory +import Language.Haskell.Extension +import DynFlags (DynFlags, xopt_set, xopt_unset) +import GHC hiding (ModuleName) +import qualified DynFlags as GHC +import SrcLoc as GHC +import RdrName as GHC (RdrName) +import Name as GHC (Name) +import qualified Language.Haskell.TH.LanguageExtensions as GHC + +import Language.Haskell.Tools.Refactor.RefactorBase +import Language.Haskell.Tools.AST (Dom, IdDom) + +-- | The modules of a library, executable, test or benchmark. A package contains one or more module collection. +data ModuleCollection + = ModuleCollection { _mcId :: ModuleCollectionId + , _mcRoot :: FilePath + , _mcSourceDirs :: [FilePath] + , _mcModules :: Map.Map SourceFileKey ModuleRecord + , _mcFlagSetup :: DynFlags -> IO DynFlags -- ^ Sets up the ghc environment for compiling the modules of this collection + , _mcDependencies :: [ModuleCollectionId] + } + +instance Eq ModuleCollection where + (==) = (==) `on` _mcId + +instance Show ModuleCollection where + show (ModuleCollection id root srcDirs mods _ deps) + = "ModuleCollection (" ++ show id ++ ") " ++ root ++ " " ++ show srcDirs ++ " (" ++ show mods ++ ") " ++ show deps + +data ModuleRecord + = ModuleNotLoaded { _recModuleWillNeedCode :: Bool } + | ModuleParsed { _parsedRecModule :: UnnamedModule (Dom RdrName) + , _modRecMS :: ModSummary + } + | ModuleRenamed { _renamedRecModule :: UnnamedModule (Dom Name) + , _modRecMS :: ModSummary + } + | ModuleTypeChecked { _typedRecModule :: UnnamedModule IdDom + , _modRecMS :: ModSummary + } + | ModuleCodeGenerated { _typedRecModule :: UnnamedModule IdDom + , _modRecMS :: ModSummary + } + +-- | This data structure identifies a module collection +data ModuleCollectionId = DirectoryMC FilePath + | LibraryMC String + | ExecutableMC String String + | TestSuiteMC String String + | BenchmarkMC String String + deriving (Eq, Ord, Show) + +moduleCollectionIdString :: ModuleCollectionId -> String +moduleCollectionIdString (DirectoryMC fp) = fp +moduleCollectionIdString (LibraryMC id) = id +moduleCollectionIdString (ExecutableMC _ id) = id +moduleCollectionIdString (TestSuiteMC _ id) = id +moduleCollectionIdString (BenchmarkMC _ id) = id + +moduleCollectionPkgId :: ModuleCollectionId -> Maybe String +moduleCollectionPkgId (DirectoryMC fp) = Nothing +moduleCollectionPkgId (LibraryMC id) = Just id +moduleCollectionPkgId (ExecutableMC id _) = Just id +moduleCollectionPkgId (TestSuiteMC id _) = Just id +moduleCollectionPkgId (BenchmarkMC id _) = Just id + +makeReferences ''ModuleCollection +makeReferences ''ModuleRecord + +instance Show ModuleRecord where + show (ModuleNotLoaded code) = "ModuleNotLoaded " ++ show code + show mr = GHC.moduleNameString $ GHC.moduleName $ GHC.ms_mod $ fromJust $ mr ^? modRecMS + + +lookupModuleColl :: String -> [ModuleCollection] -> Maybe (ModuleCollection) +lookupModuleColl moduleName = find (any ((moduleName ==) . (^. sfkModuleName)) . Map.keys . (^. mcModules)) + +lookupModInSCs :: SourceFileKey -> [ModuleCollection] -> Maybe (SourceFileKey, ModuleRecord) +lookupModInSCs moduleName = find ((moduleName ==) . fst) . concatMap (Map.assocs . (^. mcModules)) + +removeModule :: String -> [ModuleCollection] -> [ModuleCollection] +removeModule moduleName = map (mcModules .- Map.filterWithKey (\k v -> moduleName /= (k ^. sfkModuleName))) + +hasGeneratedCode :: SourceFileKey -> [ModuleCollection] -> Bool +hasGeneratedCode key = maybe False (\case (_, ModuleCodeGenerated {}) -> True; _ -> False) + . find ((key ==) . fst) . concatMap (Map.assocs . (^. mcModules)) + +needsGeneratedCode :: SourceFileKey -> [ModuleCollection] -> Bool +needsGeneratedCode key = maybe False (\case (_, ModuleCodeGenerated {}) -> True; (_, ModuleNotLoaded True) -> True; _ -> False) + . find ((key ==) . fst) . concatMap (Map.assocs . (^. mcModules)) + +codeGeneratedFor :: SourceFileKey -> [ModuleCollection] -> [ModuleCollection] +codeGeneratedFor key = map (mcModules .- Map.adjust (\case (ModuleTypeChecked mod ms) -> ModuleCodeGenerated mod ms + ModuleNotLoaded _ -> ModuleNotLoaded True + r -> r) key) + +isAlreadyLoaded :: SourceFileKey -> [ModuleCollection] -> Bool +isAlreadyLoaded key = maybe False (\case (_, ModuleNotLoaded {}) -> False; _ -> True) + . find ((key ==) . fst) . concatMap (Map.assocs . (^. mcModules)) + +-- | Gets all ModuleCollections from a list of source directories. It also orders the source directories that are package roots so that +-- they can be loaded in the order they are defined (no backward imports). This matters in those cases because for them there can be +-- special compilation flags. +getAllModules :: [FilePath] -> IO [ModuleCollection] +getAllModules pathes = orderMCs . concat <$> mapM getModules pathes + +-- | Sorts model collection in an order to remove all backward references. +-- Works because module collections defined by directories cannot be recursive. +orderMCs :: [ModuleCollection] -> [ModuleCollection] +orderMCs = sortBy compareMCs + where compareMCs :: ModuleCollection -> ModuleCollection -> Ordering + compareMCs mc _ | DirectoryMC _ <- (mc ^. mcId) = GT + compareMCs _ mc | DirectoryMC _ <- (mc ^. mcId) = LT + compareMCs mc1 mc2 | (mc2 ^. mcId) `elem` (mc1 ^. mcDependencies) = GT + compareMCs mc1 mc2 | (mc1 ^. mcId) `elem` (mc2 ^. mcDependencies) = LT + compareMCs _ _ = EQ + + -- | Get modules of the project with the indicated root directory. -- If there is a cabal file, it uses that, otherwise it just scans the directory recursively for haskell sourcefiles. -getModules :: FilePath -> IO [([FilePath], [String])] +-- Only returns the non-boot haskell modules, the boot modules will be found during loading. +getModules :: FilePath -> IO [ModuleCollection] getModules root = do files <- listDirectory root case find (\p -> takeExtension p == ".cabal") files of - Just cabalFile -> modulesFromCabalFile (root </> cabalFile) + Just cabalFile -> modulesFromCabalFile root cabalFile Nothing -> do mods <- modulesFromDirectory root root - return [([root], mods)] - - -modulesFromCabalFile :: FilePath -> IO [([FilePath], [String])] --- now adding all conditional entries, regardless of flags -modulesFromCabalFile cabal = getModules . flattenPackageDescription <$> readPackageDescription silent cabal - where getModules :: PackageDescription -> [([FilePath], [String])] - getModules pkg = map (\(bi, mods) -> ( map (normalise . (takeDirectory cabal </>)) $ hsSourceDirs bi - , map (concat . intersperse "." . components) mods) ) - $ maybe [] ((:[]) . libRecord) (library pkg) ++ map exeRecord (executables pkg) - ++ map testRecord (testSuites pkg) ++ map benchRecord (benchmarks pkg) - libRecord lib = (libBuildInfo lib, libModules lib) - exeRecord exe = (buildInfo exe, exeModules exe) - testRecord test = (testBuildInfo test, testModules test) - benchRecord bench = (benchmarkBuildInfo bench, benchmarkModules bench) + return [ModuleCollection (DirectoryMC root) root [root] (Map.fromList $ map ((, ModuleNotLoaded False) . SourceFileKey NormalHs) mods) return []] modulesFromDirectory :: FilePath -> FilePath -> IO [String] -- now recognizing only .hs files @@ -43,8 +160,202 @@ else if takeExtension path == ".hs" then return [concat $ intersperse "." $ splitDirectories $ dropExtension $ makeRelative root path] else return [] - + srcDirFromRoot :: FilePath -> String -> FilePath srcDirFromRoot fileName "" = fileName srcDirFromRoot fileName moduleName = srcDirFromRoot (takeDirectory fileName) (dropWhile (/= '.') $ dropWhile (== '.') moduleName) + +modulesFromCabalFile :: FilePath -> FilePath -> IO [ModuleCollection] +-- now adding all conditional entries, regardless of flags +modulesFromCabalFile root cabal = getModules . flattenPackageDescription <$> readPackageDescription silent (root </> cabal) + where getModules pkg = maybe [] ((:[]) . toModuleCollection pkg) (library pkg) + ++ map (toModuleCollection pkg) (executables pkg) + ++ map (toModuleCollection pkg) (testSuites pkg) + ++ map (toModuleCollection pkg) (benchmarks pkg) + + toModuleCollection :: ToModuleCollection tmc => PackageDescription -> tmc -> ModuleCollection + toModuleCollection pkg tmc = let bi = getBuildInfo tmc + in ModuleCollection (mkModuleCollKey (pkgName $ package pkg) tmc) + root + (map (normalise . (root </>)) $ hsSourceDirs bi) + (Map.fromList $ map ((, ModuleNotLoaded False) . SourceFileKey NormalHs . moduleName) (getModuleNames tmc)) + (flagsFromBuildInfo bi) + (map (\(Dependency pkgName _) -> LibraryMC (unPackageName pkgName)) (targetBuildDepends bi)) + + moduleName = concat . intersperse "." . components + +class ToModuleCollection t where + mkModuleCollKey :: PackageName -> t -> ModuleCollectionId + getBuildInfo :: t -> BuildInfo + getModuleNames :: t -> [ModuleName] + +instance ToModuleCollection Library where + mkModuleCollKey pn _ = LibraryMC (unPackageName pn) + getBuildInfo = libBuildInfo + getModuleNames = libModules + +instance ToModuleCollection Executable where + mkModuleCollKey pn exe = ExecutableMC (unPackageName pn) (exeName exe) + getBuildInfo = buildInfo + getModuleNames = exeModules + +instance ToModuleCollection TestSuite where + mkModuleCollKey pn test = TestSuiteMC (unPackageName pn) (testName test) + getBuildInfo = testBuildInfo + getModuleNames = testModules + +instance ToModuleCollection Benchmark where + mkModuleCollKey pn test = BenchmarkMC (unPackageName pn) (benchmarkName test) + getBuildInfo = benchmarkBuildInfo + getModuleNames = benchmarkModules + + +compileInContext :: ModuleCollection -> [ModuleCollection] -> DynFlags -> IO DynFlags +compileInContext mc mcs dfs + = (\dfs' -> applyDependencies mcs (mc ^. mcDependencies) dfs') + <$> (mc ^. mcFlagSetup $ dfs) + +applyDependencies :: [ModuleCollection] -> [ModuleCollectionId] -> DynFlags -> DynFlags +applyDependencies mcs ids dfs = dfs { GHC.packageFlags = catMaybes $ map (dependencyToPkgFlag mcs) ids } + +dependencyToPkgFlag :: [ModuleCollection] -> ModuleCollectionId -> Maybe (GHC.PackageFlag) +dependencyToPkgFlag mcs lib@(LibraryMC pkgName) + = if isNothing $ find (\mc -> (mc ^. mcId) == lib) mcs + then Just $ GHC.ExposePackage pkgName (GHC.PackageArg pkgName) (GHC.ModRenaming True []) + else Nothing +dependencyToPkgFlag _ _ = Nothing + +enableAllPackages :: [ModuleCollection] -> DynFlags -> DynFlags +enableAllPackages mcs dfs = applyDependencies mcs allDeps dfs + where allDeps = mcs ^? traversal & mcDependencies & traversal + +flagsFromBuildInfo :: BuildInfo -> DynFlags -> IO DynFlags +-- the import pathes are already set globally +flagsFromBuildInfo BuildInfo{ defaultExtensions, options } df + = do (df,_,_) <- parseDynamicFlags df (map (L noSrcSpan) $ concatMap snd options) + return $ foldl (.) id (map (\case EnableExtension ext -> translateExtension ext + _ -> id + ) defaultExtensions) + $ df + where translateExtension OverlappingInstances = flip xopt_set GHC.OverlappingInstances + translateExtension UndecidableInstances = flip xopt_set GHC.UndecidableInstances + translateExtension IncoherentInstances = flip xopt_set GHC.IncoherentInstances + translateExtension DoRec = flip xopt_set GHC.RecursiveDo + translateExtension RecursiveDo = flip xopt_set GHC.RecursiveDo + translateExtension ParallelListComp = flip xopt_set GHC.ParallelListComp + translateExtension MultiParamTypeClasses = flip xopt_set GHC.MultiParamTypeClasses + translateExtension MonomorphismRestriction = flip xopt_set GHC.MonomorphismRestriction + translateExtension FunctionalDependencies = flip xopt_set GHC.FunctionalDependencies + translateExtension RankNTypes = flip xopt_set GHC.RankNTypes + translateExtension ExistentialQuantification = flip xopt_set GHC.ExistentialQuantification + translateExtension ScopedTypeVariables = flip xopt_set GHC.ScopedTypeVariables + translateExtension PatternSignatures = flip xopt_set GHC.PatternSynonyms + translateExtension ImplicitParams = flip xopt_set GHC.ImplicitParams + translateExtension FlexibleContexts = flip xopt_set GHC.FlexibleContexts + translateExtension FlexibleInstances = flip xopt_set GHC.FlexibleInstances + translateExtension EmptyDataDecls = flip xopt_set GHC.EmptyDataDecls + translateExtension CPP = flip xopt_set GHC.Cpp + translateExtension KindSignatures = flip xopt_set GHC.KindSignatures + translateExtension BangPatterns = flip xopt_set GHC.BangPatterns + translateExtension TypeSynonymInstances = flip xopt_set GHC.TypeSynonymInstances + translateExtension TemplateHaskell = flip xopt_set GHC.TemplateHaskell + translateExtension ForeignFunctionInterface = flip xopt_set GHC.ForeignFunctionInterface + translateExtension Arrows = flip xopt_set GHC.Arrows + translateExtension ImplicitPrelude = flip xopt_set GHC.ImplicitPrelude + translateExtension NamedFieldPuns = flip xopt_set GHC.RecordPuns + translateExtension PatternGuards = flip xopt_set GHC.PatternGuards + translateExtension GeneralizedNewtypeDeriving = flip xopt_set GHC.GeneralizedNewtypeDeriving + translateExtension RestrictedTypeSynonyms = flip xopt_unset GHC.LiberalTypeSynonyms + translateExtension MagicHash = flip xopt_set GHC.MagicHash + translateExtension TypeFamilies = flip xopt_set GHC.TypeFamilies + translateExtension StandaloneDeriving = flip xopt_set GHC.StandaloneDeriving + translateExtension UnicodeSyntax = flip xopt_set GHC.UnicodeSyntax + translateExtension UnliftedFFITypes = flip xopt_set GHC.UnliftedFFITypes + translateExtension InterruptibleFFI = flip xopt_set GHC.InterruptibleFFI + translateExtension CApiFFI = flip xopt_set GHC.CApiFFI + translateExtension LiberalTypeSynonyms = flip xopt_set GHC.LiberalTypeSynonyms + translateExtension TypeOperators = flip xopt_set GHC.TypeOperators + translateExtension RecordWildCards = flip xopt_set GHC.RecordWildCards + translateExtension RecordPuns = flip xopt_set GHC.RecordPuns + translateExtension DisambiguateRecordFields = flip xopt_set GHC.DisambiguateRecordFields + translateExtension TraditionalRecordSyntax = flip xopt_set GHC.TraditionalRecordSyntax + translateExtension OverloadedStrings = flip xopt_set GHC.OverloadedStrings + translateExtension GADTs = flip xopt_set GHC.GADTs + translateExtension GADTSyntax = flip xopt_set GHC.GADTSyntax + translateExtension MonoPatBinds = flip xopt_set GHC.MonoPatBinds + translateExtension RelaxedPolyRec = flip xopt_set GHC.RelaxedPolyRec + translateExtension ExtendedDefaultRules = flip xopt_set GHC.ExtendedDefaultRules + translateExtension UnboxedTuples = flip xopt_set GHC.UnboxedTuples + translateExtension DeriveDataTypeable = flip xopt_set GHC.DeriveDataTypeable + translateExtension DeriveGeneric = flip xopt_set GHC.DeriveGeneric + translateExtension DefaultSignatures = flip xopt_set GHC.DefaultSignatures + translateExtension InstanceSigs = flip xopt_set GHC.InstanceSigs + translateExtension ConstrainedClassMethods = flip xopt_set GHC.ConstrainedClassMethods + translateExtension PackageImports = flip xopt_set GHC.PackageImports + translateExtension ImpredicativeTypes = flip xopt_set GHC.ImpredicativeTypes + translateExtension PostfixOperators = flip xopt_set GHC.PostfixOperators + translateExtension QuasiQuotes = flip xopt_set GHC.QuasiQuotes + translateExtension TransformListComp = flip xopt_set GHC.TransformListComp + translateExtension MonadComprehensions = flip xopt_set GHC.MonadComprehensions + translateExtension ViewPatterns = flip xopt_set GHC.ViewPatterns + translateExtension TupleSections = flip xopt_set GHC.TupleSections + translateExtension GHCForeignImportPrim = flip xopt_set GHC.GHCForeignImportPrim + translateExtension NPlusKPatterns = flip xopt_set GHC.NPlusKPatterns + translateExtension DoAndIfThenElse = flip xopt_set GHC.DoAndIfThenElse + translateExtension MultiWayIf = flip xopt_set GHC.MultiWayIf + translateExtension LambdaCase = flip xopt_set GHC.LambdaCase + translateExtension RebindableSyntax = flip xopt_set GHC.RebindableSyntax + translateExtension ExplicitForAll = flip xopt_set GHC.ExplicitForAll + translateExtension DatatypeContexts = flip xopt_set GHC.DatatypeContexts + translateExtension MonoLocalBinds = flip xopt_set GHC.MonoLocalBinds + translateExtension DeriveFunctor = flip xopt_set GHC.DeriveFunctor + translateExtension DeriveTraversable = flip xopt_set GHC.DeriveTraversable + translateExtension DeriveFoldable = flip xopt_set GHC.DeriveFoldable + translateExtension NondecreasingIndentation = flip xopt_set GHC.NondecreasingIndentation + translateExtension ConstraintKinds = flip xopt_set GHC.ConstraintKinds + translateExtension PolyKinds = flip xopt_set GHC.PolyKinds + translateExtension DataKinds = flip xopt_set GHC.DataKinds + translateExtension ParallelArrays = flip xopt_set GHC.ParallelArrays + translateExtension RoleAnnotations = flip xopt_set GHC.RoleAnnotations + translateExtension OverloadedLists = flip xopt_set GHC.OverloadedLists + translateExtension EmptyCase = flip xopt_set GHC.EmptyCase + translateExtension AutoDeriveTypeable = flip xopt_set GHC.AutoDeriveTypeable + translateExtension NegativeLiterals = flip xopt_set GHC.NegativeLiterals + translateExtension BinaryLiterals = flip xopt_set GHC.BinaryLiterals + translateExtension NumDecimals = flip xopt_set GHC.NumDecimals + translateExtension NullaryTypeClasses = flip xopt_set GHC.NullaryTypeClasses + translateExtension ExplicitNamespaces = flip xopt_set GHC.ExplicitNamespaces + translateExtension AllowAmbiguousTypes = flip xopt_set GHC.AllowAmbiguousTypes + translateExtension JavaScriptFFI = flip xopt_set GHC.JavaScriptFFI + translateExtension PatternSynonyms = flip xopt_set GHC.PatternSynonyms + translateExtension PartialTypeSignatures = flip xopt_set GHC.PartialTypeSignatures + translateExtension NamedWildCards = flip xopt_set GHC.NamedWildCards + translateExtension DeriveAnyClass = flip xopt_set GHC.DeriveAnyClass + translateExtension DeriveLift = flip xopt_set GHC.DeriveLift + translateExtension StaticPointers = flip xopt_set GHC.StaticPointers + translateExtension StrictData = flip xopt_set GHC.StrictData + translateExtension Strict = flip xopt_set GHC.Strict + translateExtension ApplicativeDo = flip xopt_set GHC.ApplicativeDo + translateExtension DuplicateRecordFields = flip xopt_set GHC.DuplicateRecordFields + translateExtension TypeApplications = flip xopt_set GHC.TypeApplications + translateExtension TypeInType = flip xopt_set GHC.TypeInType + translateExtension UndecidableSuperClasses = flip xopt_set GHC.UndecidableSuperClasses + translateExtension MonadFailDesugaring = flip xopt_set GHC.MonadFailDesugaring + translateExtension TemplateHaskellQuotes = flip xopt_set GHC.TemplateHaskellQuotes + translateExtension OverloadedLabels = flip xopt_set GHC.OverloadedLabels + + translateExtension Safe = \df -> df { GHC.safeHaskell = GHC.Sf_Safe } + translateExtension SafeImports = \df -> df { GHC.safeHaskell = GHC.Sf_Safe } + translateExtension Trustworthy = \df -> df { GHC.safeHaskell = GHC.Sf_Trustworthy } + translateExtension Unsafe = \df -> df { GHC.safeHaskell = GHC.Sf_Unsafe } + + -- Couldn't find the equivalent of these extensions + translateExtension Rank2Types = id + translateExtension PolymorphicComponents = id + translateExtension Generics = id + translateExtension ExtensibleRecords = id + translateExtension NewQualifiedOperators = id + translateExtension XmlSyntax = id + translateExtension HereDocuments = id + translateExtension RegularPatterns = id
Language/Haskell/Tools/Refactor/ListOperations.hs view
@@ -4,23 +4,27 @@ import Data.String import Data.List import Control.Reference +import Debug.Trace import Data.Function (on) import Language.Haskell.Tools.AST import Language.Haskell.Tools.AST.Rewrite import Language.Haskell.Tools.Transform filterList :: (Ann e dom SrcTemplateStage -> Bool) -> AnnListG e dom SrcTemplateStage -> AnnListG e dom SrcTemplateStage --- QUESTION: is it OK? No problem from losing separators? -filterList pred ls = replaceList (filter pred (ls ^. annListElems)) ls - --- | Replaces the list with a new one with the given elements, keeping the most common separator as the new one. -replaceList :: [Ann e dom SrcTemplateStage] -> AnnListG e dom SrcTemplateStage -> AnnListG e dom SrcTemplateStage -replaceList elems (AnnListG (NodeInfo sema src) _) - = AnnListG (NodeInfo sema (listSep mostCommonSeparator)) elems - where mostCommonSeparator - = case group $ sort (src ^. srcTmpSeparators) of - [] -> src ^. srcTmpDefaultSeparator - nonempty@(_:_) -> head $ maximumBy (compare `on` length) nonempty +filterList pred (AnnListG (NodeInfo sema src) elems) + = let (filteredElems, separators) = filterElems elems (src ^. srcTmpSeparators) + in AnnListG (NodeInfo sema (srcTmpSeparators .= separators $ src)) filteredElems + where filterElems (elem:ls) (sep:seps) + | pred elem = let (elems',seps') = filterElems' ls (sep:seps) in (elem:elems', seps') + | otherwise = filterElems ls seps + filterElems elems [] = (filter pred elems, []) + filterElems [] seps = ([], seps) + + filterElems' (elem:ls) (sep:seps) + | pred elem = let (elems',seps') = filterElems' ls seps in (elem:elems', sep:seps') + | otherwise = filterElems' ls seps + filterElems' elems [] = (filter pred elems, []) + filterElems' [] seps = ([], seps) -- | Inserts the element in the places where the two positioning functions (one checks the element before, one the element after) -- allows the placement.
Language/Haskell/Tools/Refactor/Perform.hs view
@@ -40,6 +40,7 @@ import Language.Haskell.Tools.Refactor.Predefined.GenerateExports import Language.Haskell.Tools.Refactor.Predefined.RenameDefinition import Language.Haskell.Tools.Refactor.Predefined.ExtractBinding +import Language.Haskell.Tools.Refactor.Predefined.InlineBinding import Language.Haskell.Tools.Refactor.RefactorBase import Language.Haskell.Tools.Refactor.GetModules import Language.Haskell.Tools.Refactor.Prepare @@ -61,14 +62,10 @@ where selectCommand NoRefactor = localRefactoring return selectCommand OrganizeImports = localRefactoring organizeImports selectCommand GenerateExports = localRefactoring generateExports - selectCommand (GenerateSignature sp) = localRefactoring $ generateTypeSignature' (correctSp mod sp) - selectCommand (RenameDefinition sp str) = renameDefinition' (correctSp mod sp) str - selectCommand (ExtractBinding sp str) = localRefactoring $ extractBinding' (correctSp mod sp) str - - correctSp mod sp = mkRealSrcSpan (updateSrcFile fileName $ realSrcSpanStart sp) - (updateSrcFile fileName $ realSrcSpanEnd sp) - fileName = case srcSpanStart $ getRange (snd mod) of RealSrcLoc loc -> srcLocFile loc - updateSrcFile fn loc = mkRealSrcLoc fn (srcLocLine loc) (srcLocCol loc) + selectCommand (GenerateSignature sp) = localRefactoring $ generateTypeSignature' (correctRefactorSpan (snd mod) sp) + selectCommand (RenameDefinition sp str) = renameDefinition' (correctRefactorSpan (snd mod) sp) str + selectCommand (ExtractBinding sp str) = localRefactoring $ extractBinding' (correctRefactorSpan (snd mod) sp) str + selectCommand (InlineBinding sp) = inlineBinding (correctRefactorSpan (snd mod) sp) -- | A refactoring command data RefactorCommand = NoRefactor @@ -77,18 +74,20 @@ | GenerateSignature RealSrcSpan | RenameDefinition RealSrcSpan String | ExtractBinding RealSrcSpan String + | InlineBinding RealSrcSpan deriving Show -readCommand :: String -> String -> RefactorCommand -readCommand fileName (splitOn " " -> refact:args) = analyzeCommand fileName refact args +readCommand :: String -> RefactorCommand +readCommand (splitOn " " -> refact:args) = analyzeCommand refact args -analyzeCommand :: String -> String -> [String] -> RefactorCommand -analyzeCommand _ "" _ = NoRefactor -analyzeCommand _ "CheckSource" _ = NoRefactor -analyzeCommand _ "OrganizeImports" _ = OrganizeImports -analyzeCommand _ "GenerateExports" _ = GenerateExports -analyzeCommand fileName "GenerateSignature" [sp] = GenerateSignature (readSrcSpan fileName sp) -analyzeCommand fileName "RenameDefinition" [sp, newName] = RenameDefinition (readSrcSpan fileName sp) newName -analyzeCommand fileName "ExtractBinding" [sp, newName] = ExtractBinding (readSrcSpan fileName sp) newName -analyzeCommand _ ref _ = error $ "Unknown command: " ++ ref +analyzeCommand :: String -> [String] -> RefactorCommand +analyzeCommand "" _ = NoRefactor +analyzeCommand "CheckSource" _ = NoRefactor +analyzeCommand "OrganizeImports" _ = OrganizeImports +analyzeCommand "GenerateExports" _ = GenerateExports +analyzeCommand "GenerateSignature" [sp] = GenerateSignature (readSrcSpan sp) +analyzeCommand "RenameDefinition" [sp, newName] = RenameDefinition (readSrcSpan sp) newName +analyzeCommand "ExtractBinding" [sp, newName] = ExtractBinding (readSrcSpan sp) newName +analyzeCommand "InlineBinding" [sp] = InlineBinding (readSrcSpan sp) +analyzeCommand ref _ = error $ "Unknown command: " ++ ref
Language/Haskell/Tools/Refactor/Predefined/DataToNewtype.hs view
@@ -3,7 +3,7 @@ import Language.Haskell.Tools.Refactor import Control.Reference -tryItOut moduleName = tryRefactor (localRefactoring $ dataToNewtype) moduleName +tryItOut moduleName = tryRefactor (\_ -> localRefactoring dataToNewtype) moduleName "" dataToNewtype :: Domain dom => LocalRefactoring dom dataToNewtype = return . (modDecl & annList .- changeDeclaration)
Language/Haskell/Tools/Refactor/Predefined/DollarApp.hs view
@@ -15,8 +15,7 @@ import Data.Generics.Uniplate.Data import Debug.Trace -tryItOut moduleName sp - = tryRefactor (localRefactoring $ dollarApp (readSrcSpan (toFileName "." moduleName) sp)) moduleName +tryItOut = tryRefactor (localRefactoring . dollarApp) type DollarMonad dom = StateT [SrcSpan] (LocalRefactor dom) type DollarDomain dom = (HasImportInfo dom, HasModuleInfo dom, HasFixityInfo dom, HasNameInfo dom)
Language/Haskell/Tools/Refactor/Predefined/ExtractBinding.hs view
@@ -20,11 +20,14 @@ import Data.Generics.Uniplate.Data import Control.Reference import Control.Monad.State +import Control.Monad.Identity import Language.Haskell.Tools.Refactor type ExtractBindingDomain dom = ( HasNameInfo dom, HasDefiningInfo dom, HasScopeInfo dom ) +tryItOut mod sp name = tryRefactor (localRefactoring . flip extractBinding' name) mod sp + extractBinding' :: ExtractBindingDomain dom => RealSrcSpan -> String -> LocalRefactoring dom extractBinding' sp name mod = if isValidBindingName name then extractBinding (nodesContaining sp) (nodesContaining sp) name mod @@ -73,20 +76,23 @@ addLocalBinding declRange exprRange local bind = do done <- get if not done then do put True - return $ doAddBinding declRange exprRange local bind + return $ indentBody $ doAddBinding declRange exprRange local bind else return bind where - doAddBinding declRng _ local sb@(SimpleBind {}) = valBindLocals .- insertLocalBind declRng local $ sb + doAddBinding declRng _ local sb@(SimpleBind {}) = valBindLocals .- insertLocalBind local $ sb doAddBinding declRng (RealSrcSpan rng) local fb@(FunctionBind {}) = funBindMatches & annList & filtered (isInside rng) & matchBinds - .- insertLocalBind declRng local $ fb + .- insertLocalBind local $ fb + indentBody = (valBindRhs .- updIndent) . (funBindMatches & annList & matchLhs .- updIndent) . (funBindMatches & annList & matchRhs .- updIndent) + + updIndent :: SourceInfoTraversal elem => elem dom SrcTemplateStage -> elem dom SrcTemplateStage + updIndent = setMinimalIndent 4 + -- | Puts a value definition into a list of local binds -insertLocalBind :: SrcSpan -> ValueBind dom -> MaybeLocalBinds dom -> MaybeLocalBinds dom -insertLocalBind declRng toInsert locals - | isAnnNothing locals - , RealSrcSpan rng <- declRng = -- creates the new where clause indented 2 spaces from the declaration - mkLocalBinds (srcLocCol (realSrcSpanStart rng) + 2) [mkLocalValBind toInsert] +insertLocalBind :: ValueBind dom -> MaybeLocalBinds dom -> MaybeLocalBinds dom +insertLocalBind toInsert locals + | isAnnNothing locals = mkLocalBinds [mkLocalValBind toInsert] | otherwise = annJust & localBinds .- insertWhere (mkLocalValBind toInsert) (const True) isNothing $ locals -- | All expressions that are bound stronger than function application.
Language/Haskell/Tools/Refactor/Predefined/GenerateTypeSignature.hs view
@@ -5,6 +5,7 @@ , TypeApplications , TypeFamilies , ConstraintKinds + , TupleSections #-} module Language.Haskell.Tools.Refactor.Predefined.GenerateTypeSignature (generateTypeSignature, generateTypeSignature', GenerateSignatureDomain) where @@ -15,6 +16,7 @@ import Outputable as GHC import TysWiredIn as GHC import Id as GHC +import Unique as GHC import Data.List import Data.Maybe @@ -26,8 +28,10 @@ import Language.Haskell.Tools.Refactor as AST -type GenerateSignatureDomain dom = ( HasModuleInfo dom, HasIdInfo dom, HasImportInfo dom ) +type GenerateSignatureDomain dom = ( HasModuleInfo dom, HasIdInfo dom, HasImportInfo dom, HasScopeInfo dom ) +tryItOut = tryRefactor (localRefactoring . generateTypeSignature') + generateTypeSignature' :: GenerateSignatureDomain dom => RealSrcSpan -> LocalRefactoring dom generateTypeSignature' sp = generateTypeSignature (nodesContaining sp) (nodesContaining sp) (getValBindInList sp) @@ -39,29 +43,55 @@ -> (forall d . (BindingElem d) => AnnList d dom -> Maybe (ValueBind dom)) -- ^ Selector for either local or top-level declaration in the definition list -> LocalRefactoring dom -generateTypeSignature topLevelRef localRef vbAccess - = flip evalStateT False . - (topLevelRef !~ genTypeSig vbAccess - <=< localRef !~ genTypeSig vbAccess) +generateTypeSignature topLevelRef localRef vbAccess mod + = let typeSigs = universeBi mod + bindings = universeBi mod + findTypeSigFor id = find (\ts -> any (id ==) $ map semanticsId (ts ^? tsName & annList & simpleName)) + bindsWithSigs = catMaybes $ concatMap (\b -> map (\n -> let id = semanticsId n in fmap (id,,b) (findTypeSigFor id typeSigs)) (b ^? bindingName)) bindings + scopedSigs = hasScopedTypeSignatures mod + in flip evalStateT False . + (topLevelRef !~ genTypeSig scopedSigs bindsWithSigs vbAccess + <=< localRef !~ genTypeSig scopedSigs bindsWithSigs vbAccess) $ mod -genTypeSig :: (GenerateSignatureDomain dom, BindingElem d) => (AnnList d dom -> Maybe (ValueBind dom)) +hasScopedTypeSignatures :: Module dom -> Bool +hasScopedTypeSignatures mod = "ScopedTypeVariables" `elem` (mod ^? filePragmas & annList & lpPragmas & annList & langExt :: [String]) + +genTypeSig :: forall dom d . (GenerateSignatureDomain dom, BindingElem d) => Bool -> [(GHC.Var, TypeSignature dom, ValueBind dom)] -> (AnnList d dom -> Maybe (ValueBind dom)) -> AnnList d dom -> StateT Bool (LocalRefactor dom) (AnnList d dom) -genTypeSig vbAccess ls +genTypeSig scopedSigs sigBinds vbAccess ls | Just vb <- vbAccess ls , not (typeSignatureAlreadyExist ls vb) - = do let id = getBindingName vb - isTheBind (Just decl) - = isBinding decl && map semanticsId (decl ^? elementName) == map semanticsId (vb ^? bindingName) - isTheBind _ = False - - alreadyGenerated <- get - if alreadyGenerated - then return ls - else do put True - typeSig <- lift $ generateTSFor (getName id) (idType id) - return $ insertWhere (createTypeSig typeSig) (const True) isTheBind ls + = if isSimpleBinding vb + then + do let id = getBindingName vb + isTheBind (Just decl) + = isBinding decl && map semanticsId (decl ^? elementName) == map semanticsId (vb ^? bindingName) + isTheBind _ = False + + alreadyGenerated <- get + if alreadyGenerated + then return ls + else do put True + -- checking for possible situations when we cannot generate signature because of + -- an implicitly passed value + let dangerousTypeVars = dangerousTVs vb scopedSigs sigBinds + myTvs = concatMap @[] (getExternalTVs . idType . semanticsId) (vb ^? bindingName) + if not $ null @[] $ myTvs `intersect` dangerousTypeVars + then refactError $ "Could not generate type signature: the type variable(s) " + ++ concat (intersperse ", " $ map (showSDocUnsafe . ppr) (myTvs `intersect` dangerousTypeVars)) + ++ " cannot be captured. (Use ScopedTypeVariables and forall-ed type signatures)" + else do + typeSig <- lift $ generateTSFor (getName id) (idType id) + return $ insertWhere (createTypeSig typeSig) (const True) isTheBind ls + else refactError "Signature can only be generated for simple value bindings." | otherwise = return ls - + where isSimpleBinding vb = case vb of SimpleBind (AST.VarPat {}) _ _ -> True + SimpleBind _ _ _ -> False + _ -> True + dangerousTVs vb scopedSigs sigBinds + = let dangerousDecls = if scopedSigs then filter (\(_,ts,_) -> not $ isForalledTS ts) sigBinds else sigBinds + dangerousNames = map (\(_,_,bn) -> bn ^? (valBindPats & biplateRef &+& bindingName)) dangerousDecls + in concatMap (concatMap @[] (getExternalTVs . idType . semanticsId @(QualifiedName dom))) dangerousNames generateTSFor :: GenerateSignatureDomain dom => GHC.Name -> GHC.Type -> LocalRefactor dom (TypeSignature dom) generateTSFor n t = mkTypeSignature (mkUnqualName' n) <$> generateTypeFor (-1) (dropForAlls t) @@ -132,3 +162,15 @@ [n] -> n [] -> error "Trying to generate a signature for a binding with no name" _ -> error "Trying to generate a signature for a binding with multiple names" + +-- * Checking for type variable constraints + +getExternalTVs :: GHC.Type -> [GHC.Var] +getExternalTVs t + | Just tv <- getTyVar_maybe t = [tv] + | Just (op, arg) <- splitAppTy_maybe t = getExternalTVs op `union` getExternalTVs arg + | Just (tv, t') <- splitForAllTy_maybe t = delete tv $ getExternalTVs t' + | otherwise = [] + +isForalledTS :: TypeSignature dom -> Bool +isForalledTS ts = not $ null @[] $ ts ^? tsType & typeBounded & annList
Language/Haskell/Tools/Refactor/Predefined/IfToGuards.hs view
@@ -6,7 +6,7 @@ import SrcLoc import Data.Generics.Uniplate.Data -tryItOut moduleName sp = tryRefactor (localRefactoring $ ifToGuards (readSrcSpan (toFileName "." moduleName) sp)) moduleName +tryItOut = tryRefactor (localRefactoring . ifToGuards) ifToGuards :: Domain dom => RealSrcSpan -> LocalRefactoring dom ifToGuards sp = return . (nodesContaining sp .- changeBindings)
+ Language/Haskell/Tools/Refactor/Predefined/InlineBinding.hs view
@@ -0,0 +1,223 @@+{-# LANGUAGE RankNTypes + , ConstraintKinds + , FlexibleContexts + , TypeFamilies + , LambdaCase + , TypeApplications + , ScopedTypeVariables + , MultiWayIf + #-} +-- | Defines the inline binding refactoring that removes a value binding and replaces all occurences +-- with an expression equivalent to the body of the binding. +module Language.Haskell.Tools.Refactor.Predefined.InlineBinding (inlineBinding, InlineBindingDomain) where + +import Control.Reference +import Control.Monad.Writer hiding (Alt) +import Control.Monad.State +import Data.Maybe +import Data.List (nub) +import Data.Either (isLeft) +import Data.Generics.Uniplate.Operations +import Data.Generics.Uniplate.Data + +import SrcLoc as GHC +import Name as GHC + +import Language.Haskell.Tools.Refactor as AST +import Language.Haskell.Tools.AST as AST + +import Debug.Trace + +tryItOut = tryRefactor inlineBinding + +type InlineBindingDomain dom = ( HasNameInfo dom, HasDefiningInfo dom, HasScopeInfo dom, HasModuleInfo dom ) + +inlineBinding :: forall dom . InlineBindingDomain dom => RealSrcSpan -> Refactoring dom +inlineBinding span namedMod@(_,mod) mods + = let topLevel :: Simple Traversal (Module dom) (DeclList dom) + topLevel = nodesContaining span + local :: Simple Traversal (Module dom) (LocalBindList dom) + local = nodesContaining span + elemAccess :: (BindingElem d) => AnnList d dom -> Maybe (ValueBind dom) + elemAccess = getValBindInList span + removed = catMaybes $ map elemAccess (mod ^? topLevel) ++ map elemAccess (mod ^? local) + in case reverse removed of + [] -> refactError "No binding is selected." + removedBinding:_ -> + let [removedBindingName] = nub $ catMaybes $ map semanticsName (removedBinding ^? bindingName) + in if | any (containInlined removedBindingName) mods + -> refactError "Cannot inline the definition, it is used in other modules." + | _:_ <- mod ^? modHead & annJust & mhExports & annJust & biplateRef + & filtered (\n -> semanticsName (n :: QualifiedName dom) == Just removedBindingName) + -> refactError "Cannot inline the definition, it is present in the export list." + | otherwise -> localRefactoring (inlineBinding' topLevel local removedBinding removedBindingName) namedMod mods + +-- | Performs the inline binding on a single module. +inlineBinding' :: InlineBindingDomain dom + => Simple Traversal (Module dom) (DeclList dom) + -> Simple Traversal (Module dom) (LocalBindList dom) + -> ValueBind dom -> GHC.Name + -> LocalRefactoring dom +inlineBinding' topLevelRef localRef removedBinding removedBindingName mod + = do replacement <- createReplacement removedBinding + let RealSrcSpan bindingSpan = getRange removedBinding + (mod', used) <- runStateT (descendBiM (replaceInvocations bindingSpan removedBindingName replacement) mod) False + if not used + then refactError "The selected definition is not used, it can be safely deleted." + else return $ removeBindingAndSig topLevelRef localRef removedBindingName mod' + +-- | True if the given module contains the name of the inlined definition. +containInlined :: forall dom . InlineBindingDomain dom => GHC.Name -> ModuleDom dom -> Bool +containInlined name (_,mod) + = any (\qn -> semanticsName qn == Just name) $ (mod ^? biplateRef :: [QualifiedName dom]) + +-- | Removes the inlined binding and the accompanying type and fixity signatures. +removeBindingAndSig :: InlineBindingDomain dom + => Simple Traversal (Module dom) (DeclList dom) + -> Simple Traversal (Module dom) (LocalBindList dom) + -> GHC.Name -> AST.Module dom + -> AST.Module dom +removeBindingAndSig topLevelRef localRef name + = (topLevelRef .- removeBindingAndSig' name) . (localRef .- removeBindingAndSig' name) + +removeBindingAndSig' :: (InlineBindingDomain dom, BindingElem d) => GHC.Name -> AnnList d dom -> AnnList d dom +removeBindingAndSig' name = (annList .- removeNameFromSigBind) . filterList notThatBindOrSig + where notThatBindOrSig e + | Just sb <- e ^? sigBind = nub (map semanticsName (sb ^? tsName & annList & simpleName)) /= [Just name] + | Just vb <- e ^? valBind = nub (map semanticsName (vb ^? bindingName)) /= [Just name] + | Just fs <- e ^? fixitySig = nub (map semanticsName (fs ^? fixityOperators & annList & operatorName)) /= [Just name] + | otherwise = True + + removeNameFromSigBind d + | Just sb <- d ^? sigBind + = createTypeSig $ tsName .- filterList (\n -> semanticsName (n ^. simpleName) /= Just name) $ sb + | Just fs <- d ^? fixitySig + = createFixitySig $ fixityOperators .- filterList (\n -> semanticsName (n ^. operatorName) /= Just name) $ fs + | otherwise = d + +-- | As a top-down transformation, replaces the occurrences of the binding with generated expressions. This method passes +-- the captured arguments of the function call to generate simpler results. +replaceInvocations :: InlineBindingDomain dom + => RealSrcSpan -> GHC.Name -> ([[GHC.Name]] -> [Expr dom] -> Expr dom) -> Expr dom -> StateT Bool (LocalRefactor dom) (Expr dom) +replaceInvocations bindingRange name replacement expr + | (Var n, args) <- splitApps expr + , semanticsName (n ^. simpleName) == Just name + = case getRange expr of + RealSrcSpan ownRange | bindingRange `containsSpan` ownRange + -> lift $ refactError "Cannot inline definitions containing direct recursion." + _ -> do put True + replacement (semanticsScope expr) <$> mapM (descendM (replaceInvocations bindingRange name replacement)) args + | otherwise + = descendM (replaceInvocations bindingRange name replacement) expr + +-- | Splits an application into function and arguments. Works also for operators. +splitApps :: Expr dom -> (Expr dom, [Expr dom]) +splitApps (App f a) = case splitApps f of (fun, args) -> (fun, args ++ [a]) +splitApps (InfixApp l (NormalOp qn) r) = (mkVar (mkParenName qn), [l,r]) +splitApps (InfixApp l (BacktickOp qn) r) = (mkVar (mkNormalName qn), [l,r]) +splitApps (Paren expr) = splitApps expr +splitApps expr = (expr, []) + +-- | Rejoins the function and the arguments as an expression. +joinApps :: Expr dom -> [Expr dom] -> Expr dom +joinApps f [] = f +joinApps f args = parenIfNeeded (foldl mkApp f args) + +-- | Create an expression that is equivalent to calling the given bind. +createReplacement :: InlineBindingDomain dom => ValueBind dom -> LocalRefactor dom ([[GHC.Name]] -> [Expr dom] -> Expr dom) +createReplacement (SimpleBind (VarPat _) (UnguardedRhs e) locals) + = return $ \_ args -> joinApps (parenIfNeeded $ wrapLocals locals e) args +createReplacement (SimpleBind _ _ _) + = refactError "Cannot inline, illegal simple bind. Only variable left-hand sides and unguarded right-hand sides are accepted." +createReplacement (FunctionBind (AnnList [Match lhs (UnguardedRhs expr) locals])) + = return $ \_ args -> let (argReplacement, matchedPats, appliedArgs) = matchArguments (getArgsOf lhs) args + in joinApps (parenIfNeeded (createLambda matchedPats (wrapLocals locals (replaceExprs argReplacement expr)))) appliedArgs + where getArgsOf (MatchLhs _ (AnnList args)) = args + getArgsOf (InfixLhs lhs _ rhs (AnnList more)) = lhs:rhs:more +createReplacement (FunctionBind matches) + = return $ \sc args -> let numArgs = getArgNum (head (matches ^? annList & matchLhs)) - length args + newArgs = take numArgs $ map mkName $ filter notInScope $ map (("x" ++ ) . show @Int) [1..] + notInScope str = not $ any (any ((== str) . occNameString . getOccName)) sc + in parenIfNeeded $ createLambda (map mkVarPat newArgs) + $ mkCase (mkTuple $ map mkVar newArgs ++ args) + $ map replaceMatch (matches ^? annList) + where getArgNum (MatchLhs n (AnnList args)) = length args + getArgNum (InfixLhs _ _ _ (AnnList more)) = length more + 2 + +-- | Replaces names with expressions according to a mapping. +replaceExprs :: InlineBindingDomain dom => [(GHC.Name, Expr dom)] -> Expr dom -> Expr dom +replaceExprs [] = id +replaceExprs replaces = (uniplateRef .-) $ \case + Var n | Just name <- semanticsName (n ^. simpleName) + , Just replace <- lookup name replaces + -> replace + e -> e + +-- | Matches a pattern list with an expression list and generates bindings. Matches until an argument cannot be matched. +matchArguments :: InlineBindingDomain dom => [Pattern dom] -> [Expr dom] -> ([(GHC.Name, Expr dom)], [Pattern dom], [Expr dom]) +matchArguments (ParenPat p : pats) exprs = matchArguments (p:pats) exprs +matchArguments (p:pats) (e:exprs) + | Just replacement <- staticPatternMatch p e + = case matchArguments pats exprs of (replacements, patterns, expressions) -> (replacement ++ replacements, patterns, expressions) + | otherwise + = ([], p:pats, e:exprs) +matchArguments pats [] = ([], pats, []) +matchArguments [] exprs = ([], [], exprs) + +-- | Matches a pattern with an expression. Generates a mapping of names to expressions. +staticPatternMatch :: InlineBindingDomain dom => Pattern dom -> Expr dom -> Maybe [(GHC.Name, Expr dom)] +staticPatternMatch (VarPat n) e + | Just name <- semanticsName $ n ^. simpleName + = Just [(name, e)] +staticPatternMatch (AppPat n (AnnList args)) e + | (Var n', exprs) <- splitApps e + , length args == length exprs + && semanticsName (n ^. simpleName) == semanticsName (n' ^. simpleName) + , Just subs <- sequence $ zipWith staticPatternMatch args exprs + = Just $ concat subs +staticPatternMatch (TuplePat (AnnList pats)) (Tuple (AnnList args)) + | length pats == length args + , Just subs <- sequence $ zipWith staticPatternMatch pats args + = Just $ concat subs +staticPatternMatch p e = Nothing + +replaceMatch :: Match dom -> Alt dom +replaceMatch (Match lhs rhs locals) = mkAlt (toPattern lhs) (toAltRhs rhs) (locals ^? annJust) + where toPattern (MatchLhs _ (AnnList pats)) = mkTuplePat pats + toPattern (InfixLhs lhs _ rhs (AnnList more)) = mkTuplePat (lhs:rhs:more) + + toAltRhs (UnguardedRhs expr) = mkCaseRhs expr + toAltRhs (GuardedRhss (AnnList rhss)) = mkGuardedCaseRhss (map toAltGuardedRhs rhss) + + toAltGuardedRhs (GuardedRhs (AnnList guards) expr) = mkGuardedCaseRhs guards expr + +wrapLocals :: MaybeLocalBinds dom -> Expr dom -> Expr dom +wrapLocals bnds = case bnds ^? annJust & localBinds & annList of + [] -> id + localBinds -> mkLet localBinds + +-- | True for patterns that need to be parenthesized if in a lambda +compositePat :: Pattern dom -> Bool +compositePat (AppPat {}) = True +compositePat (InfixAppPat {}) = True +compositePat (TypeSigPat {}) = True +compositePat (ViewPat {}) = True +compositePat _ = False + +parenIfNeeded :: Expr dom -> Expr dom +parenIfNeeded e = if compositeExprs e then mkParen e else e + +-- | True for expresssions that need to be parenthesized if in application +compositeExprs :: Expr dom -> Bool +compositeExprs (App {}) = True +compositeExprs (InfixApp {}) = True +compositeExprs (Lambda {}) = True +compositeExprs (Let {}) = True +compositeExprs (If {}) = True +compositeExprs (Case {}) = True +compositeExprs (Do {}) = True +compositeExprs _ = False + +createLambda :: [Pattern dom] -> Expr dom -> Expr dom +createLambda [] = id +createLambda pats = mkLambda (map (\p -> if compositePat p then mkParenPat p else p) pats)
Language/Haskell/Tools/Refactor/Predefined/OrganizeImports.hs view
@@ -77,7 +77,7 @@ = do let Just specName = semanticsName =<< (spec ^? ieName&simpleName) Just tt <- GHC.lookupName (getName specName) let subspecsInScope = case tt of ATyCon tc | not (isClassTyCon tc) - -> map getName (tyConDataCons tc) `intersect` usedNames + -> (map getName (tyConDataCons tc) ++ map flSelector (tyConFieldLabels tc)) `intersect` usedNames _ -> usedNames ieSubspec&annJust !- narrowImportSubspecs subspecsInScope $ spec
Language/Haskell/Tools/Refactor/Predefined/RenameDefinition.hs view
@@ -44,11 +44,18 @@ renameModule :: forall dom . DomainRenameDefinition dom => String -> String -> Refactoring dom renameModule from to m mods - | any (nameConflict to) (map snd $ m:mods) = refactError "UName conflict when renaming module" + | any (nameConflict to) (map snd $ m:mods) = refactError "Name conflict when renaming module" | not (validModuleName to) = refactError "The given name is not a valid module name" - | otherwise = fmap (\ls -> ModuleRemoved from : map (\(ContentChanged (mod,res)) -> ContentChanged (if mod == from then to else mod, res)) ls) + | otherwise = -- here it is important that the delete is the last, because rename + -- can still use the info about the deleted module + fmap (\ls -> map (alterChange from to) ls ++ [ModuleRemoved from]) $ localRefactoring (replaceModuleNames >=> alterNormalNames) m mods - where replaceModuleNames :: LocalRefactoring dom + where alterChange from to (ContentChanged (mod,res)) + | (mod ^. sfkModuleName) == from + = ModuleCreated to res (SourceFileKey NormalHs from) + alterChange _ _ c = c + + replaceModuleNames :: LocalRefactoring dom replaceModuleNames = biplateRef @_ @(ModuleName dom) & filtered (\e -> (e ^. moduleNameString) == from) != mkModuleName to alterNormalNames :: LocalRefactoring dom
Language/Haskell/Tools/Refactor/Prepare.hs view
@@ -14,6 +14,7 @@ module Language.Haskell.Tools.Refactor.Prepare where import GHC hiding (loadModule) +import qualified GHC (loadModule) import Panic (handleGhcException) import Outputable import BasicTypes @@ -28,10 +29,16 @@ import DynFlags import StringBuffer +import Control.Monad import Control.Monad.IO.Class import System.FilePath import Data.Maybe +import Data.List (isInfixOf, (\\)) import Data.List.Split +import System.Info (os) +import System.Directory +import Data.IntSet (member) +import Language.Haskell.TH.LanguageExtensions import Language.Haskell.Tools.AST as AST import Language.Haskell.Tools.AST.FromGHC @@ -39,16 +46,24 @@ import Language.Haskell.Tools.Transform import Language.Haskell.Tools.Refactor.RefactorBase -tryRefactor :: Refactoring IdDom -> String -> IO () -tryRefactor refact moduleName +tryRefactor :: (RealSrcSpan -> Refactoring IdDom) -> String -> String -> IO () +tryRefactor refact moduleName span = runGhc (Just libdir) $ do initGhcFlags useDirs ["."] mod <- loadModule "." moduleName >>= parseTyped - res <- runRefactor (toFileName "." moduleName, mod) [] refact + res <- runRefactor (SourceFileKey NormalHs moduleName, mod) [] + $ refact $ correctRefactorSpan mod $ readSrcSpan span case res of Right r -> liftIO $ mapM_ (putStrLn . prettyPrint . snd . fromContentChanged) r Left err -> liftIO $ putStrLn err +-- | Adjust the source range to be applied to the refactored module +correctRefactorSpan :: UnnamedModule dom -> RealSrcSpan -> RealSrcSpan +correctRefactorSpan mod sp = mkRealSrcSpan (updateSrcFile fileName $ realSrcSpanStart sp) + (updateSrcFile fileName $ realSrcSpanEnd sp) + where fileName = case srcSpanStart $ getRange mod of RealSrcLoc loc -> srcLocFile loc + updateSrcFile fn loc = mkRealSrcLoc fn (srcLocLine loc) (srcLocCol loc) + -- | Set the given flags for the GHC session useFlags :: [String] -> Ghc [String] useFlags args = do @@ -60,42 +75,62 @@ -- | Initialize GHC flags to default values that support refactoring initGhcFlags :: Ghc () -initGhcFlags = do +initGhcFlags = initGhcFlags' False + +initGhcFlagsForTest :: Ghc () +initGhcFlagsForTest = initGhcFlags' True + +initGhcFlags' :: Bool -> Ghc () +initGhcFlags' needsCodeGen = do dflags <- getSessionDynFlags - setSessionDynFlags + void $ setSessionDynFlags $ flip gopt_set Opt_KeepRawTokenStream $ flip gopt_set Opt_NoHsMain $ dflags { importPaths = [] - , hscTarget = HscAsm -- needed for static pointers - , ghcLink = LinkInMemory + , hscTarget = if needsCodeGen then HscInterpreted else HscNothing + , ghcLink = if needsCodeGen then LinkInMemory else NoLink , ghcMode = CompManager , packageFlags = ExposePackage "template-haskell" (PackageArg "template-haskell") (ModRenaming True []) : packageFlags dflags } - return () -- | Use the given source directories useDirs :: [FilePath] -> Ghc () useDirs workingDirs = do dynflags <- getSessionDynFlags - setSessionDynFlags dynflags { importPaths = importPaths dynflags ++ workingDirs } - return () + void $ setSessionDynFlags dynflags { importPaths = importPaths dynflags ++ workingDirs } + +deregisterDirs :: [FilePath] -> Ghc () +deregisterDirs workingDirs = do + dynflags <- getSessionDynFlags + void $ setSessionDynFlags dynflags { importPaths = importPaths dynflags \\ workingDirs } -- | Translates module name and working directory into the name of the file where the given module should be defined -toFileName :: String -> String -> FilePath +toFileName :: FilePath -> String -> FilePath toFileName workingDir mod = normalise $ workingDir </> map (\case '.' -> pathSeparator; c -> c) mod ++ ".hs" -- | Translates module name and working directory into the name of the file where the boot module should be defined -toBootFileName :: String -> String -> FilePath +toBootFileName :: FilePath -> String -> FilePath toBootFileName workingDir mod = normalise $ workingDir </> map (\case '.' -> pathSeparator; c -> c) mod ++ ".hs-boot" +getSourceDir :: ModSummary -> IO FilePath +getSourceDir ms + = do filePath <- canonicalizePath $ getModSumOrig ms + let modNameParts = splitOn "." $ GHC.moduleNameString (moduleName (ms_mod ms)) + filePathParts = splitPath filePath + let srcDirParts = reverse $ drop (length modNameParts) $ reverse filePathParts + return $ joinPath srcDirParts + +getModSumOrig :: ModSummary -> FilePath +getModSumOrig = normalise . fromMaybe (error "getModSumOrig: The given module doesn't have haskell source file.") . ml_hs_file . ms_location + -- | Load the summary of a module given by the working directory and module name. loadModule :: String -> String -> Ghc ModSummary loadModule workingDir moduleName - = do initGhcFlags + = do initGhcFlagsForTest useDirs [workingDir] target <- guessTarget moduleName Nothing setTargets [target] - load LoadAllTargets + load (LoadUpTo $ mkModuleName moduleName) getModSummary $ mkModuleName moduleName -- | The final version of our AST, with type infromation added @@ -103,25 +138,57 @@ -- | Get the typed representation from a type-correct program. parseTyped :: ModSummary -> Ghc TypedModule -parseTyped modSum = do - p <- parseModule modSum +parseTyped modSum = withAlteredDynFlags (return . normalizeFlags) $ do + let compExts = extensionFlags $ ms_hspp_opts modSum + hasStaticFlags = fromEnum StaticPointers `member` compExts + ms = if hasStaticFlags then forceAsmGen (modSumNormalizeFlags modSum) else (modSumNormalizeFlags modSum) + p <- parseModule ms tc <- typecheckModule p + GHC.loadModule tc -- when used with loadModule, the module will be loaded twice let annots = pm_annotations p srcBuffer = fromJust $ ms_hspp_buf $ pm_mod_summary p prepareAST srcBuffer . placeComments (getNormalComments $ snd annots) <$> (addTypeInfos (typecheckedSource tc) - =<< (do parseTrf <- runTrf (fst annots) (getPragmaComments $ snd annots) $ trfModule modSum (pm_parsed_source p) + =<< (do parseTrf <- runTrf (fst annots) (getPragmaComments $ snd annots) $ trfModule ms (pm_parsed_source p) runTrf (fst annots) (getPragmaComments $ snd annots) - $ trfModuleRename modSum parseTrf + $ trfModuleRename ms parseTrf (fromJust $ tm_renamed_source tc) (pm_parsed_source p))) -data IsBoot = NormalHs | IsHsBoot deriving (Eq, Ord, Show) +-- | Modifies the dynamic flags for performing a ghc task +withAlteredDynFlags :: GhcMonad m => (DynFlags -> m DynFlags) -> m a -> m a +withAlteredDynFlags modDFs action = do + dfs <- getSessionDynFlags + setSessionDynFlags =<< modDFs dfs + res <- action + setSessionDynFlags dfs + return res -readSrcSpan :: String -> String -> RealSrcSpan -readSrcSpan fileName s = case splitOn "-" s of - [from,to] -> mkRealSrcSpan (readSrcLoc fileName from) (readSrcLoc fileName to) +-- | Forces the code generation for a given module +forceCodeGen :: ModSummary -> ModSummary +forceCodeGen ms = ms { ms_hspp_opts = modOpts' } + where modOpts = (ms_hspp_opts ms) { hscTarget = HscInterpreted } + modOpts' = modOpts { ghcLink = LinkInMemory } + +-- | Forces ASM code generation for a given module +forceAsmGen :: ModSummary -> ModSummary +forceAsmGen ms = ms { ms_hspp_opts = modOpts' } + where modOpts = (ms_hspp_opts ms) { hscTarget = HscAsm } + modOpts' = modOpts { ghcLink = LinkInMemory } + +-- | Normalizes the flags for a module summary +modSumNormalizeFlags :: ModSummary -> ModSummary +modSumNormalizeFlags ms = ms { ms_hspp_opts = normalizeFlags (ms_hspp_opts ms) } + +-- | Removes all flags that are unintelligable for refactoring +normalizeFlags :: DynFlags -> DynFlags +normalizeFlags = updOptLevel 0 + +readSrcSpan :: String -> RealSrcSpan +readSrcSpan s = case splitOn "-" s of + [one] -> mkRealSrcSpan (readSrcLoc one) (readSrcLoc one) + [from,to] -> mkRealSrcSpan (readSrcLoc from) (readSrcLoc to) -readSrcLoc :: String -> String -> RealSrcLoc -readSrcLoc fileName s = case splitOn ":" s of - [line,col] -> mkRealSrcLoc (mkFastString fileName) (read line) (read col)+readSrcLoc :: String -> RealSrcLoc +readSrcLoc s = case splitOn ":" s of + [line,col] -> mkRealSrcLoc (mkFastString "file-name-should-be-fixed") (read line) (read col)
Language/Haskell/Tools/Refactor/RefactorBase.hs view
@@ -7,6 +7,7 @@ , FlexibleContexts , TypeSynonymInstances , MultiWayIf + , TemplateHaskell #-} module Language.Haskell.Tools.Refactor.RefactorBase where @@ -35,8 +36,17 @@ type UnnamedModule dom = Ann AST.UModule dom SrcTemplateStage -- | The name of the module and the AST -type ModuleDom dom = (String, UnnamedModule dom) +type ModuleDom dom = (SourceFileKey, UnnamedModule dom) +-- | Module name and marker to separate .hs-boot module definitions. Specifies a source file in a working directory. +data SourceFileKey = SourceFileKey { _sfkIsBoot :: IsBoot + , _sfkModuleName :: String + } + deriving (Eq, Ord, Show) + +-- | Decides if a module is a .hs-boot file or a normal .hs file +data IsBoot = NormalHs | IsHsBoot deriving (Eq, Ord, Show) + -- | A refactoring that only affects one module type LocalRefactoring dom = UnnamedModule dom -> LocalRefactor dom (UnnamedModule dom) @@ -46,7 +56,16 @@ -- | Change in the project, modification or removal of a module. data RefactorChange dom = ContentChanged { fromContentChanged :: (ModuleDom dom) } | ModuleRemoved { removedModuleName :: String } + | ModuleCreated { createdModuleName :: String + , createdModuleContent :: UnnamedModule dom + , sameLocation :: SourceFileKey + } +instance Show (RefactorChange dom) where + show (ContentChanged (n, _)) = "ContentChanged (" ++ show n ++ ")" + show (ModuleRemoved n) = "ModuleRemoved " ++ n + show (ModuleCreated n _ other) = "ModuleCreated " ++ n ++ " (" ++ show other ++ ")" + -- | Performs the given refactoring, transforming it into a Ghc action runRefactor :: (HasModuleInfo dom) => ModuleDom dom -> [ModuleDom dom] -> Refactoring dom -> Ghc (Either String [RefactorChange dom]) runRefactor mod mods trf = runExceptT $ trf mod mods @@ -71,7 +90,7 @@ addGeneratedImports :: [GHC.Name] -> Ann UModule dom SrcTemplateStage -> Ann UModule dom SrcTemplateStage addGeneratedImports names m = modImports&annListElems .- (++ addImports names) $ m where addImports :: [GHC.Name] -> [Ann UImportDecl dom SrcTemplateStage] - addImports names = map createImport $ groupBy ((==) `on` GHC.nameModule) $ nub $ sort names + addImports names = map createImport $ groupBy ((==) `on` GHC.nameModule) $ filter (isJust . GHC.nameModule_maybe) $ nub $ sort names -- TODO: group names like constructors into correct IESpecs createImport :: [GHC.Name] -> Ann UImportDecl dom SrcTemplateStage @@ -86,6 +105,17 @@ gcatch w c = WriterT (runWriterT w `gcatch` (runWriterT . c)) gmask m = WriterT $ gmask (\f -> runWriterT $ m (WriterT . f . runWriterT)) +instance (Monad m, HasDynFlags m) => HasDynFlags (StateT s m) where + getDynFlags = lift getDynFlags + +instance (GhcMonad m) => GhcMonad (StateT s m) where + getSession = lift getSession + setSession env = lift (setSession env) + +instance (ExceptionMonad m) => ExceptionMonad (StateT s m) where + gcatch r c = StateT (\ctx -> runStateT r ctx `gcatch` (flip runStateT ctx . c)) + gmask m = StateT $ \ctx -> gmask (\f -> runStateT (m (\a -> StateT $ \ctx' -> f (runStateT a ctx'))) ctx) + instance GhcMonad m => GhcMonad (ReaderT s m) where getSession = lift getSession setSession env = lift (setSession env) @@ -242,4 +272,6 @@ nameValid _ _ = False isIdStartChar c = (isLetter c && isAscii c) || c == '\'' || c == '_' -isOperatorChar c = (isPunctuation c || isSymbol c) && isAscii c+isOperatorChar c = (isPunctuation c || isSymbol c) && isAscii c + +makeReferences ''SourceFileKey
+ Language/Haskell/Tools/Refactor/Session.hs view
@@ -0,0 +1,234 @@+{-# LANGUAGE TemplateHaskell + , TupleSections + #-} +module Language.Haskell.Tools.Refactor.Session where + +import qualified Data.Map as Map +import qualified Data.List as List +import Data.Maybe +import Data.Function (on) +import Control.Monad.State +import Control.Reference +import System.IO +import System.FilePath +import Debug.Trace + +import GHC +import GhcMonad as GHC +import HscTypes as GHC +import Digraph as GHC +import DynFlags as GHC +import FastString as GHC +import Data.IntSet (member) +import Language.Haskell.TH.LanguageExtensions + +import Language.Haskell.Tools.AST (IdDom, semanticsModule) +import Language.Haskell.Tools.Refactor.Prepare +import Language.Haskell.Tools.Refactor.GetModules +import Language.Haskell.Tools.Refactor.RefactorBase + +data RefactorSessionState + = RefactorSessionState { __refSessMCs :: [ModuleCollection] + } + +makeReferences ''RefactorSessionState + +class IsRefactSessionState st where + refSessMCs :: Simple Lens st [ModuleCollection] + initSession :: st + +instance IsRefactSessionState RefactorSessionState where + refSessMCs = _refSessMCs + initSession = RefactorSessionState [] + + +loadPackagesFrom :: IsRefactSessionState st => (ModSummary -> IO a) -> [FilePath] -> StateT st Ghc ([a], [String]) +loadPackagesFrom report packages = + do modColls <- liftIO $ getAllModules packages + modify $ refSessMCs .- (++ modColls) + allModColls <- gets (^. refSessMCs) + lift $ useDirs (modColls ^? traversal & mcSourceDirs & traversal) + let (ignored, modNames) = extractDuplicates $ map (^. sfkModuleName) $ concat $ map Map.keys $ modColls ^? traversal & mcModules + alreadyExistingMods = concatMap (map (^. sfkModuleName) . Map.keys . (^. mcModules)) (allModColls List.\\ modColls) + lift $ mapM addTarget $ map (\mod -> (Target (TargetModule (GHC.mkModuleName mod)) True Nothing)) modNames + withAlteredDynFlags (return . enableAllPackages allModColls) $ do + modsForColls <- lift $ depanal [] True + let modsToParse = flattenSCCs $ topSortModuleGraph False modsForColls Nothing + actuallyCompiled = filter (not . (`elem` alreadyExistingMods) . modSumName) modsToParse + checkEvaluatedMods report modsToParse + mods <- mapM (loadModule report) actuallyCompiled + return (mods, ignored) + + where extractDuplicates :: Eq a => [a] -> ([a],[a]) + extractDuplicates (a:rest) + = case extractDuplicates rest of (repl, orig) -> if a `elem` orig then (a:repl, orig) else (repl, a:orig) + extractDuplicates [] = ([],[]) + + loadModule :: IsRefactSessionState st => (ModSummary -> IO a) -> ModSummary -> StateT st Ghc a + loadModule report ms = do + needsCodeGen <- gets (needsGeneratedCode (keyFromMS ms) . (^. refSessMCs)) + reloadModule report (if needsCodeGen then forceCodeGen ms else ms) + +keyFromMS :: ModSummary -> SourceFileKey +keyFromMS ms = SourceFileKey (case ms_hsc_src ms of HsSrcFile -> NormalHs; _ -> IsHsBoot) (modSumName ms) + +getMods :: (Monad m, IsRefactSessionState st) + => Maybe SourceFileKey -> StateT st m ( Maybe (SourceFileKey, UnnamedModule IdDom) + , [(SourceFileKey, UnnamedModule IdDom)] ) +getMods actMod + = do mcs <- gets (^. refSessMCs) + return $ ( (_2 !~ (^? typedRecModule)) =<< flip lookupModInSCs mcs =<< actMod + , filter ((actMod /=) . Just . fst) $ concatMap (catMaybes . map (_2 !~ (^? typedRecModule)) . Map.assocs . (^. mcModules)) mcs ) + +getFileMods :: (GhcMonad m, IsRefactSessionState st) + => FilePath -> StateT st m ( Maybe (SourceFileKey, UnnamedModule IdDom) + , [(SourceFileKey, UnnamedModule IdDom)] ) +getFileMods fname + = do mcs <- gets (^. refSessMCs) + let mods = map (\(k,m) -> (fromJust $ m ^? modRecMS, k)) + (concatMap Map.assocs $ (mcs ^? traversal & mcModules :: [Map.Map SourceFileKey ModuleRecord])) + let sfs = catMaybes $ map (\(ms,k) -> if Just fname == fmap normalise (ml_hs_file (ms_location ms)) then Just k else Nothing) mods + case sfs of sf:_ -> getMods (Just sf) + [] -> getMods Nothing + +reloadChangedModules :: IsRefactSessionState st => (ModSummary -> IO a) -> (ModSummary -> Bool) -> StateT st Ghc [a] +reloadChangedModules report isChanged = do + reachable <- getReachableModules isChanged + checkEvaluatedMods report reachable + mapM (reloadModule report) reachable + +getReachableModules :: IsRefactSessionState st => (ModSummary -> Bool) -> StateT st Ghc [ModSummary] +getReachableModules selected = do + allModColls <- gets (^. refSessMCs) + withAlteredDynFlags (return . enableAllPackages allModColls) $ do + allMods <- lift $ depanal [] True + let (allModsGraph, lookup) = moduleGraphNodes False allMods + changedMods = catMaybes $ map (\ms -> lookup (ms_hsc_src ms) (moduleName $ ms_mod ms)) + $ filter selected allMods + recompMods = map (ms_mod . getModFromNode) $ reachablesG (transposeG allModsGraph) changedMods + sortedMods = reverse $ topologicalSortG allModsGraph + return $ filter ((`elem` recompMods) . ms_mod) $ map getModFromNode sortedMods + +reloadModule :: IsRefactSessionState st => (ModSummary -> IO a) -> ModSummary -> StateT st Ghc a +reloadModule report ms = do + let modName = modSumName ms + mcs <- gets (^. refSessMCs) + let Just mc = lookupModuleColl modName mcs + codeGen = hasGeneratedCode (keyFromMS ms) mcs + let dfs = ms_hspp_opts ms + dfs' <- liftIO $ compileInContext mc mcs dfs + let ms' = ms { ms_hspp_opts = dfs' } + newm <- lift $ withAlteredDynFlags (liftIO . compileInContext mc mcs) $ + parseTyped (if codeGen then forceCodeGen ms' else ms') + modify $ refSessMCs & traversal & filtered (\mc' -> (mc' ^. mcRoot) == (mc ^. mcRoot)) & mcModules + .- Map.insert (keyFromMS ms) ((if codeGen then ModuleCodeGenerated else ModuleTypeChecked) newm ms) + liftIO $ report ms + +checkEvaluatedMods :: IsRefactSessionState st => (ModSummary -> IO a) -> [ModSummary] -> StateT st Ghc [a] +checkEvaluatedMods report mods = do + modsNeedCode <- lift (getEvaluatedMods mods) + mcs <- gets (^. refSessMCs) + res <- forM modsNeedCode $ \ms -> reloadIfNeeded ms mcs + return $ catMaybes res + where reloadIfNeeded ms mcs + = let key = keyFromMS ms + in if not (hasGeneratedCode key mcs) + then do modify $ refSessMCs .- codeGeneratedFor key + if (isAlreadyLoaded key mcs) then + -- The module is already loaded but code is not generated. Need to reload. + Just <$> lift (codeGenForModule report (codeGeneratedFor key mcs) ms) + else return Nothing + else return Nothing + +codeGenForModule :: (ModSummary -> IO a) -> [ModuleCollection] -> ModSummary -> Ghc a +codeGenForModule report mcs ms + = let modName = modSumName ms + Just mc = lookupModuleColl modName mcs + Just rec = lookupModInSCs (keyFromMS ms) mcs + in -- TODO: don't recompile, only load? + do withAlteredDynFlags (liftIO . compileInContext mc mcs) + $ parseTyped (forceCodeGen ms) + liftIO $ report ms + +-- | Check which modules can be reached from the module, if it uses template haskell. +getEvaluatedMods :: [ModSummary] -> Ghc [GHC.ModSummary] +-- We cannot really get the modules that need to be linked, because we cannot rename splice content if the +-- module is not type checked and that is impossible if the splice cannot be evaluated. +getEvaluatedMods mods + = do allMods <- getModuleGraph + let (allModsGraph, lookup) = moduleGraphNodes False allMods + modsWithTH = catMaybes $ map (\ms -> lookup (ms_hsc_src ms) (moduleName $ ms_mod ms)) $ filter isTH mods + recompMods = map (moduleName . ms_mod . getModFromNode) $ reachablesG allModsGraph modsWithTH + sortedMods = map getModFromNode $ reverse $ topologicalSortG allModsGraph + sortedTHMods = filter ((`elem` recompMods) . moduleName . ms_mod) sortedMods + return sortedTHMods + where isTH mod = fromEnum TemplateHaskell `member` extensionFlags (ms_hspp_opts mod) + + +modSumName :: ModSummary -> String +modSumName = GHC.moduleNameString . moduleName . ms_mod + +-- * code copied from GHC because it is not public in GhcMake module + +type NodeKey = (ModuleName, IsBoot) +type NodeMap a = Map.Map NodeKey a +type SummaryNode = (ModSummary, Int, [Int]) + +getModFromNode :: SummaryNode -> ModSummary +getModFromNode (ms, _, _) = ms + +moduleGraphNodes :: Bool -> [ModSummary] + -> (Graph SummaryNode, HscSource -> ModuleName -> Maybe SummaryNode) +moduleGraphNodes drop_hs_boot_nodes summaries = (graphFromEdgedVertices nodes, lookup_node) + where + numbered_summaries = zip summaries [1..] + + lookup_node :: HscSource -> ModuleName -> Maybe SummaryNode + lookup_node hs_src mod = Map.lookup (mod, hscSourceToIsBoot hs_src) node_map + + lookup_key :: HscSource -> ModuleName -> Maybe Int + lookup_key hs_src mod = fmap summaryNodeKey (lookup_node hs_src mod) + + node_map :: NodeMap SummaryNode + node_map = Map.fromList [ ((moduleName (ms_mod s), + hscSourceToIsBoot (ms_hsc_src s)), node) + | node@(s, _, _) <- nodes ] + + nodes :: [SummaryNode] + nodes = [ (s, key, out_keys) + | (s, key) <- numbered_summaries + , not (isBootSummary s && drop_hs_boot_nodes) + , let out_keys = out_edge_keys hs_boot_key (map unLoc (ms_home_srcimps s)) ++ + out_edge_keys HsSrcFile (map unLoc (ms_home_imps s)) ++ + (-- see [boot-edges] below + if drop_hs_boot_nodes || ms_hsc_src s == HsBootFile + then [] + else case lookup_key HsBootFile (ms_mod_name s) of + Nothing -> [] + Just k -> [k]) ] + + hs_boot_key | drop_hs_boot_nodes = HsSrcFile + | otherwise = HsBootFile + + out_edge_keys :: HscSource -> [ModuleName] -> [Int] + out_edge_keys hi_boot ms = mapMaybe (lookup_key hi_boot) ms + +hscSourceToIsBoot :: HscSource -> IsBoot +hscSourceToIsBoot HsBootFile = IsHsBoot +hscSourceToIsBoot _ = NormalHs + +summaryNodeKey :: SummaryNode -> Int +summaryNodeKey (_, k, _) = k + +ms_home_imps :: ModSummary -> [Located ModuleName] +ms_home_imps = home_imps . ms_imps + +ms_home_srcimps :: ModSummary -> [Located ModuleName] +ms_home_srcimps = home_imps . ms_srcimps + +home_imps :: [(Maybe FastString, Located ModuleName)] -> [Located ModuleName] +home_imps imps = [ lmodname | (mb_pkg, lmodname) <- imps, + isLocal mb_pkg ] + where isLocal Nothing = True + isLocal (Just pkg) | pkg == fsLit "this" = True -- "this" is special + isLocal _ = False
haskell-tools-refactor.cabal view
@@ -1,5 +1,5 @@ name: haskell-tools-refactor -version: 0.3.0.1 +version: 0.4.0.0 synopsis: Refactoring Tool for Haskell description: Contains a set of refactorings based on the Haskell-Tools framework to easily transform a Haskell program. For the descriptions of the implemented refactorings, see the homepage. homepage: https://github.com/haskell-tools/haskell-tools @@ -12,6 +12,7 @@ cabal-version: >=1.10 library + ghc-options: -O2 exposed-modules: Language.Haskell.Tools.Refactor , Language.Haskell.Tools.Refactor.BindingElem , Language.Haskell.Tools.Refactor.GetModules @@ -19,6 +20,7 @@ , Language.Haskell.Tools.Refactor.Prepare , Language.Haskell.Tools.Refactor.Perform , Language.Haskell.Tools.Refactor.ListOperations + , Language.Haskell.Tools.Refactor.Session , Language.Haskell.Tools.Refactor.Predefined.GenerateTypeSignature , Language.Haskell.Tools.Refactor.Predefined.OrganizeImports @@ -28,6 +30,7 @@ , Language.Haskell.Tools.Refactor.Predefined.DataToNewtype , Language.Haskell.Tools.Refactor.Predefined.IfToGuards , Language.Haskell.Tools.Refactor.Predefined.DollarApp + , Language.Haskell.Tools.Refactor.Predefined.InlineBinding build-depends: base >= 4.9 && < 4.10 , mtl >= 2.2 && < 2.3 @@ -42,19 +45,20 @@ , template-haskell >= 2.11 && < 2.12 , ghc >= 8.0 && < 8.1 , Cabal >= 1.24 && < 1.25 - , haskell-tools-ast >= 0.3 && < 0.4 - , haskell-tools-backend-ghc >= 0.3 && < 0.4 - , haskell-tools-rewrite >= 0.3 && < 0.4 - , haskell-tools-prettyprint >= 0.3 && < 0.4 + , haskell-tools-ast >= 0.4 && < 0.5 + , haskell-tools-backend-ghc >= 0.4 && < 0.5 + , haskell-tools-rewrite >= 0.4 && < 0.5 + , haskell-tools-prettyprint >= 0.4 && < 0.5 default-language: Haskell2010 test-suite haskell-tools-test type: exitcode-stdio-1.0 - ghc-options: -with-rtsopts=-M2g + ghc-options: -with-rtsopts=-M2g -O2 hs-source-dirs: test main-is: Main.hs build-depends: base >= 4.9 && < 4.10 - , HUnit >= 1.3 && < 1.4 + , tasty >= 0.11 && < 0.12 + , tasty-hunit >= 0.9 && < 0.10 , transformers >= 0.5 && < 0.6 , either >= 4.4 && < 4.5 , filepath >= 1.4 && < 1.5 @@ -71,9 +75,9 @@ , ghc >= 8.0 && < 8.1 , ghc-paths >= 0.1 && < 0.2 , Cabal >= 1.24 && < 1.25 - , haskell-tools-ast >= 0.3 && < 0.4 - , haskell-tools-backend-ghc >= 0.3 && < 0.4 - , haskell-tools-rewrite >= 0.3 && < 0.4 - , haskell-tools-prettyprint >= 0.3 && < 0.4 - , haskell-tools-refactor >= 0.3 && < 0.4 + , haskell-tools-ast >= 0.4 && < 0.5 + , haskell-tools-backend-ghc >= 0.4 && < 0.5 + , haskell-tools-rewrite >= 0.4 && < 0.5 + , haskell-tools-prettyprint >= 0.4 && < 0.5 + , haskell-tools-refactor >= 0.4 && < 0.5 default-language: Haskell2010
test/Main.hs view
@@ -4,20 +4,27 @@ #-} module Main where +import Test.Tasty +import Test.Tasty.HUnit + import GHC hiding (loadModule, ParsedModule) import DynFlags import GHC.Paths ( libdir ) import Module as GHC +import Control.Reference import Control.Monad.IO.Class +import Control.Monad.State import Control.Monad import Data.Maybe +import qualified Data.Map as Map import Data.List import Data.Either.Combinators -import Test.HUnit hiding (test) import System.IO import System.Exit import System.FilePath +import Data.IntSet (member) +import Language.Haskell.TH.LanguageExtensions import Language.Haskell.Tools.AST as AST import Language.Haskell.Tools.AST.Rewrite as G @@ -33,39 +40,39 @@ import Language.Haskell.Tools.Refactor.Predefined.RenameDefinition import Language.Haskell.Tools.Refactor.Predefined.ExtractBinding import Language.Haskell.Tools.Refactor.RefactorBase - +import Language.Haskell.Tools.Refactor.Session import Language.Haskell.Tools.Refactor.Predefined.DataToNewtype import Language.Haskell.Tools.Refactor.Predefined.IfToGuards import Language.Haskell.Tools.Refactor.Predefined.DollarApp main :: IO () -main = run nightlyTests - -run :: [Test] -> IO () -run tests = do results <- runTestTT $ TestList tests - if errors results + failures results > 0 - then exitFailure - else exitSuccess - -nightlyTests :: [Test] -nightlyTests = unitTests - ++ map makeCpphsTest cppHsTests - ++ map makeInstanceControlTest instanceControlTests +main = defaultMain nightlyTests -unitTests :: [Test] -unitTests = genTests ++ functionalTests +nightlyTests :: TestTree +nightlyTests + = testGroup "all tests" [ testGroup "functional tests" functionalTests + , testGroup "CppHs tests" $ map makeCpphsTest cppHsTests + , testGroup "instance-control tests" $ map makeInstanceControlTest instanceControlTests + ] -functionalTests :: [Test] -functionalTests = map makeReprintTest checkTestCases - ++ map makeOrganizeImportsTest organizeImportTests - ++ map makeGenerateSignatureTest generateSignatureTests - ++ map makeGenerateExportsTest generateExportsTests - ++ map makeRenameDefinitionTest renameDefinitionTests - ++ map makeWrongRenameDefinitionTest wrongRenameDefinitionTests - ++ map makeExtractBindingTest extractBindingTests - ++ map makeWrongExtractBindingTest wrongExtractBindingTests - ++ map makeMultiModuleTest multiModuleTests - ++ map makeMiscRefactorTest miscRefactorTests +functionalTests :: [TestTree] +functionalTests + = [ testGroup "reprint tests" (map makeReprintTest checkTestCases) + , testGroup "refactor tests" + $ map makeOrganizeImportsTest organizeImportTests + ++ map makeGenerateSignatureTest generateSignatureTests + ++ map makeWrongGenerateSigTest wrongGenerateSigTests + ++ map makeGenerateExportsTest generateExportsTests + ++ map makeRenameDefinitionTest renameDefinitionTests + ++ map makeWrongRenameDefinitionTest wrongRenameDefinitionTests + ++ map makeExtractBindingTest extractBindingTests + ++ map makeWrongExtractBindingTest wrongExtractBindingTests + ++ map makeInlineBindingTest inlineBindingTests + ++ map makeWrongInlineBindingTest wrongInlineBindingTests + ++ map (makeMultiModuleTest checkMultiResults) multiModuleTests + ++ map (makeMultiModuleTest checkMultiFail) wrongMultiModuleTests + ++ map makeMiscRefactorTest miscRefactorTests + ] where checkTestCases = languageTests ++ organizeImportTests ++ map fst generateSignatureTests @@ -74,6 +81,7 @@ ++ map (\(mod,_,_) -> mod) wrongRenameDefinitionTests ++ map (\(mod,_,_) -> mod) extractBindingTests ++ map (\(mod,_,_) -> mod) wrongExtractBindingTests + ++ map (\(mod,_) -> mod) inlineBindingTests rootDir = ".." </> ".." </> "examples" @@ -212,8 +220,19 @@ , ("Refactor.GenerateTypeSignature.Let", "3:9-3:18") , ("Refactor.GenerateTypeSignature.TypeDefinedInModule", "3:1-3:1") , ("Refactor.GenerateTypeSignature.BringToScope.AlreadyQualImport", "6:1-6:2") + , ("Refactor.GenerateTypeSignature.CanCaptureVariable", "8:10-8:10") + , ("Refactor.GenerateTypeSignature.CanCaptureVariableHasOtherDef", "8:10-8:10") ] +wrongGenerateSigTests = + [ ("Refactor.GenerateTypeSignature.CannotCaptureVariable", "7:10-7:10") + , ("Refactor.GenerateTypeSignature.CannotCaptureVariableNoExt", "8:10-8:10") + , ("Refactor.GenerateTypeSignature.ComplexLhs", "3:1") + , ("Refactor.GenerateTypeSignature.ComplexLhs", "4:1") + , ("Refactor.GenerateTypeSignature.ComplexLhs", "5:1") + , ("Refactor.GenerateTypeSignature.ComplexLhs", "6:1") + ] + generateExportsTests = [ "Refactor.GenerateExports.Normal" , "Refactor.GenerateExports.Operators" @@ -233,6 +252,7 @@ , ("Refactor.RenameDefinition.ClassMember", "7:3-7:4", "q") , ("Refactor.RenameDefinition.LocalFunction", "4:5-4:6", "g") , ("Refactor.RenameDefinition.LayoutAware", "3:1-3:2", "main") + , ("Refactor.RenameDefinition.FormattingAware", "3:1-3:2", "aa") , ("Refactor.RenameDefinition.Arg", "4:3-4:4", "y") , ("Refactor.RenameDefinition.FunTypeVar", "3:6-3:7", "x") , ("Refactor.RenameDefinition.FunTypeVarLocal", "5:10-5:11", "b") @@ -271,6 +291,11 @@ , ("Refactor.ExtractBinding.ListComprehension", "5:25-5:39", "notDivisible") , ("Refactor.ExtractBinding.Records", "5:5-5:39", "plus") , ("Refactor.ExtractBinding.RecordWildcards", "6:5-6:27", "plus") + , ("Refactor.ExtractBinding.ExistingLocalDef", "3:5-3:10", "a") + , ("Refactor.ExtractBinding.Indentation", "3:12-3:18", "extracted") + , ("Refactor.ExtractBinding.IndentationMultiLine", "3:12-3:18", "extracted") + , ("Refactor.ExtractBinding.IndentationOperator", "3:13-3:20", "extracted") + , ("Refactor.ExtractBinding.ExtractedFormatting", "4:5-5:7", "extracted") ] wrongExtractBindingTests = @@ -278,6 +303,30 @@ , ("Refactor.ExtractBinding.NameConflict", "3:19-3:27", "stms") ] +inlineBindingTests = + [ ("Refactor.InlineBinding.Simplest", "4:1-4:2") + , ("Refactor.InlineBinding.Nested", "4:1-4:2") + , ("Refactor.InlineBinding.Local", "4:9-4:10") + , ("Refactor.InlineBinding.LocalNested", "5:17-5:18") + , ("Refactor.InlineBinding.WithLocals", "4:1-4:2") + , ("Refactor.InlineBinding.MultiMatch", "4:1-4:2") + , ("Refactor.InlineBinding.SimpleMultiMatch", "4:1-4:2") + , ("Refactor.InlineBinding.AlreadyApplied", "4:1-4:2") + , ("Refactor.InlineBinding.PatternMatched", "4:1-4:2") + , ("Refactor.InlineBinding.MultiApplied", "4:1-4:2") + , ("Refactor.InlineBinding.Operator", "4:1-4:2") + , ("Refactor.InlineBinding.MultiMatchGuarded", "4:1-4:2") + , ("Refactor.InlineBinding.RemoveSignatures", "5:1-5:2") + , ("Refactor.InlineBinding.FilterSignatures", "5:1-5:2") + ] + +wrongInlineBindingTests = + [ ("Refactor.InlineBinding.Recursive", "4:1-4:2") + , ("Refactor.InlineBinding.InExportList", "4:1-4:2") + , ("Refactor.InlineBinding.NotOccurring", "3:1") + ] + + multiModuleTests = [ ("RenameDefinition 5:5-5:6 bb", "A", "Refactor" </> "RenameDefinition" </> "MultiModule", []) , ("RenameDefinition 1:8-1:9 C", "B", "Refactor" </> "RenameDefinition" </> "RenameModule", ["B"]) @@ -287,60 +336,80 @@ , ("RenameDefinition 6:1-6:4 spliceTyp", "Define", "Refactor" </> "RenameDefinition" </> "SpliceType", []) ] +wrongMultiModuleTests = + [ ("InlineBinding 3:1-3:2", "A", "Refactor" </> "InlineBinding" </> "AppearsInAnother", []) + ] + miscRefactorTests = - [ ("Refactor.DataToNewtype.Cases", \_ _ -> dataToNewtype) - , ("Refactor.IfToGuards.Simple", \wd mod -> ifToGuards (readSrcSpan (toFileName wd mod) "3:11-3:33")) - , ("Refactor.DollarApp.FirstSingle", \wd mod -> dollarApp (readSrcSpan (toFileName wd mod) "5:5-5:12")) - , ("Refactor.DollarApp.FirstMulti", \wd mod -> dollarApp (readSrcSpan (toFileName wd mod) "5:5-5:16")) - , ("Refactor.DollarApp.InfixOperator", \wd mod -> dollarApp (readSrcSpan (toFileName wd mod) "5:5-5:16")) - , ("Refactor.DollarApp.AnotherOperator", \wd mod -> dollarApp (readSrcSpan (toFileName wd mod) "5:5-5:15")) - , ("Refactor.DollarApp.ImportDollar", \wd mod -> dollarApp (readSrcSpan (toFileName wd mod) "6:5-6:12")) + [ ("Refactor.DataToNewtype.Cases", \m -> dataToNewtype) + , ("Refactor.IfToGuards.Simple", \m -> ifToGuards (correctRefactorSpan m $ readSrcSpan "3:11-3:33")) + , ("Refactor.DollarApp.FirstSingle", \m -> dollarApp (correctRefactorSpan m $ readSrcSpan "5:5-5:12")) + , ("Refactor.DollarApp.FirstMulti", \m -> dollarApp (correctRefactorSpan m $ readSrcSpan "5:5-5:16")) + , ("Refactor.DollarApp.InfixOperator", \m -> dollarApp (correctRefactorSpan m $ readSrcSpan "5:5-5:16")) + , ("Refactor.DollarApp.AnotherOperator", \m -> dollarApp (correctRefactorSpan m $ readSrcSpan "5:5-5:15")) + , ("Refactor.DollarApp.ImportDollar", \m -> dollarApp (correctRefactorSpan m $ readSrcSpan "6:5-6:12")) ] -makeMultiModuleTest :: (String, String, String, [String]) -> Test -makeMultiModuleTest (refact, mod, root, removed) - = TestLabel (root ++ ":" ++ mod) $ TestCase +makeMultiModuleTest :: ((String, String, String, [String]) -> Either String [(String, Maybe String)] -> IO ()) + -> (String, String, String, [String]) -> TestTree +makeMultiModuleTest checker test@(refact, mod, root, removed) + = testCase (root ++ ":" ++ mod) $ do res <- performRefactors refact (rootDir </> root) [] mod - case res of Right result -> checkResults result removed - Left err -> assertFailure $ "The transformation failed : " ++ err - where checkResults :: [(String, Maybe String)] -> [String] -> IO () - checkResults ((name, Just mod):rest) removed = - do expected <- loadExpected False ((rootDir </> root) ++ "_res") name - assertEqual "The transformed result is not what is expected" (standardizeLineEndings expected) - (standardizeLineEndings mod) - checkResults rest removed - checkResults ((name, Nothing) : rest) removed = checkResults rest (delete name removed) - checkResults [] [] = return () - checkResults [] removed = assertFailure $ "Modules has not been marked as removed: " ++ concat (intersperse ", " removed) + checker test res + +checkMultiResults :: (String, String, String, [String]) -> Either String [(String, Maybe String)] -> IO () +checkMultiResults _ (Left err) = assertFailure $ "The transformation failed : " ++ err +checkMultiResults test@(_,_,root,removed) (Right ((name, Just mod):rest)) = + do expected <- loadExpected False ((rootDir </> root) ++ "_res") name + assertEqual "The transformed result is not what is expected" (standardizeLineEndings expected) + (standardizeLineEndings mod) + checkMultiResults test (Right rest) +checkMultiResults (r,m,root,removed) (Right ((name, Nothing) : rest)) = checkMultiResults (r,m,root,delete name removed) (Right rest) +checkMultiResults (_,_,_,[]) (Right []) = return () +checkMultiResults (_,_,_,removed) (Right []) + = assertFailure $ "Modules has not been marked as removed: " ++ concat (intersperse ", " removed) -createTest :: String -> [String] -> String -> Test +checkMultiFail :: (String, String, String, [String]) -> Either String [(String, Maybe String)] -> IO () +checkMultiFail _ (Left _) = return () +checkMultiFail _ (Right _) = assertFailure "The transformation should fail." + +createTest :: String -> [String] -> String -> TestTree createTest refactoring args mod - = TestLabel mod $ TestCase $ checkCorrectlyTransformed (refactoring ++ (concatMap (" "++) args)) rootDir mod + = testCase mod $ checkCorrectlyTransformed (refactoring ++ (concatMap (" "++) args)) rootDir mod -createFailTest :: String -> [String] -> String -> Test +createFailTest :: String -> [String] -> String -> TestTree createFailTest refactoring args mod - = TestLabel mod $ TestCase $ checkTransformFails (refactoring ++ (concatMap (" "++) args)) rootDir mod + = testCase mod $ checkTransformFails (refactoring ++ (concatMap (" "++) args)) rootDir mod -makeOrganizeImportsTest :: String -> Test +makeOrganizeImportsTest :: String -> TestTree makeOrganizeImportsTest = createTest "OrganizeImports" [] -makeGenerateSignatureTest :: (String, String) -> Test +makeGenerateSignatureTest :: (String, String) -> TestTree makeGenerateSignatureTest (mod, rng) = createTest "GenerateSignature" [rng] mod -makeGenerateExportsTest :: String -> Test +makeGenerateExportsTest :: String -> TestTree makeGenerateExportsTest mod = createTest "GenerateExports" [] mod -makeRenameDefinitionTest :: (String, String, String) -> Test +makeRenameDefinitionTest :: (String, String, String) -> TestTree makeRenameDefinitionTest (mod, rng, newName) = createTest "RenameDefinition" [rng, newName] mod -makeWrongRenameDefinitionTest :: (String, String, String) -> Test +makeWrongRenameDefinitionTest :: (String, String, String) -> TestTree makeWrongRenameDefinitionTest (mod, rng, newName) = createFailTest "RenameDefinition" [rng, newName] mod -makeExtractBindingTest :: (String, String, String) -> Test +makeWrongGenerateSigTest :: (String, String) -> TestTree +makeWrongGenerateSigTest (mod, rng) = createFailTest "GenerateSignature" [rng] mod + +makeExtractBindingTest :: (String, String, String) -> TestTree makeExtractBindingTest (mod, rng, newName) = createTest "ExtractBinding" [rng, newName] mod - -makeWrongExtractBindingTest :: (String, String, String) -> Test + +makeWrongExtractBindingTest :: (String, String, String) -> TestTree makeWrongExtractBindingTest (mod, rng, newName) = createFailTest "ExtractBinding" [rng, newName] mod + +makeInlineBindingTest :: (String, String) -> TestTree +makeInlineBindingTest (mod, rng) = createTest "InlineBinding" [rng] mod + +makeWrongInlineBindingTest :: (String, String) -> TestTree +makeWrongInlineBindingTest (mod, rng) = createFailTest "InlineBinding" [rng] mod checkCorrectlyTransformed :: String -> String -> String -> IO () checkCorrectlyTransformed command workingDir moduleName @@ -348,21 +417,21 @@ res <- performRefactor command workingDir [] moduleName assertEqual "The transformed result is not what is expected" (Right (standardizeLineEndings expected)) (mapRight standardizeLineEndings res) -makeMiscRefactorTest :: (String, FilePath -> String -> LocalRefactoring IdDom) -> Test +makeMiscRefactorTest :: (String, UnnamedModule IdDom -> LocalRefactoring IdDom) -> TestTree makeMiscRefactorTest (moduleName, refact) - = TestLabel moduleName $ TestCase $ + = testCase moduleName $ do expected <- loadExpected True rootDir moduleName - res <- testRefactor (localRefactoring (refact rootDir moduleName)) moduleName + res <- testRefactor refact moduleName assertEqual "The transformed result is not what is expected" (Right (standardizeLineEndings expected)) - (mapRight standardizeLineEndings res) + (mapRight standardizeLineEndings res) -testRefactor :: Refactoring IdDom -> String -> IO (Either String String) +testRefactor :: (UnnamedModule IdDom -> LocalRefactoring IdDom) -> String -> IO (Either String String) testRefactor refact moduleName = runGhc (Just libdir) $ do initGhcFlags useDirs [rootDir] mod <- loadModule rootDir moduleName >>= parseTyped - res <- runRefactor (toFileName rootDir moduleName, mod) [] refact + res <- runRefactor (SourceFileKey NormalHs moduleName, mod) [] (localRefactoring $ refact mod) case res of Right r -> return $ Right $ prettyPrint $ snd $ fromContentChanged $ head r Left err -> return $ Left err @@ -379,14 +448,14 @@ standardizeLineEndings = filter (/= '\r') -makeReprintTest :: String -> Test -makeReprintTest mod = TestLabel mod $ TestCase (checkCorrectlyPrinted rootDir mod) +makeReprintTest :: String -> TestTree +makeReprintTest mod = testCase mod (checkCorrectlyPrinted rootDir mod) -makeCpphsTest :: String -> Test -makeCpphsTest mod = TestLabel mod $ TestCase (checkCorrectlyPrinted (rootDir </> "CppHs") mod) +makeCpphsTest :: String -> TestTree +makeCpphsTest mod = testCase mod (checkCorrectlyPrinted (rootDir </> "CppHs") mod) -makeInstanceControlTest :: String -> Test -makeInstanceControlTest mod = TestLabel mod $ TestCase (checkCorrectlyPrinted (rootDir </> "InstanceControl") mod) +makeInstanceControlTest :: String -> TestTree +makeInstanceControlTest mod = testCase mod (checkCorrectlyPrinted (rootDir </> "InstanceControl") mod) checkCorrectlyPrinted :: String -> String -> IO () checkCorrectlyPrinted workingDir moduleName @@ -403,161 +472,66 @@ assertEqual "The original and the transformed source differ" expected actual' assertEqual "The original and the transformed source differ" expected actual'' - performRefactors :: String -> String -> [String] -> String -> IO (Either String [(String, Maybe String)]) performRefactors command workingDir flags target = do - mods <- getModules workingDir - runGhc (Just libdir) $ do - initGhcFlags - useFlags flags - useDirs (concatMap fst mods) - setTargets (map (\mod -> (Target (TargetModule (GHC.mkModuleName mod)) True Nothing)) (concatMap snd mods)) - load LoadAllTargets - allMods <- getModuleGraph - selectedMod <- getModSummary (GHC.mkModuleName target) - let otherModules = filter (not . (\ms -> ms_mod ms == ms_mod selectedMod && ms_hsc_src ms == ms_hsc_src selectedMod)) allMods - targetMod <- parseTyped selectedMod - otherMods <- mapM parseTyped otherModules - res <- performCommand (readCommand (toFileName workingDir target) command) - (target, targetMod) (zip (map (GHC.moduleNameString . moduleName . ms_mod) otherModules) otherMods) - return $ (\case Right r -> Right $ (map (\case ContentChanged (n,m) -> (n, Just $ prettyPrint m) - ModuleRemoved m -> (m, Nothing) - )) r - Left l -> Left l) - $ res + mods <- getAllModules [workingDir] + runGhc (Just libdir) $ do + initGhcFlagsForTest + useFlags flags + useDirs [workingDir] + setTargets (map (\mod -> (Target (TargetModule (GHC.mkModuleName mod)) True Nothing)) (concatMap (map (^. sfkModuleName) . Map.keys . (^. mcModules)) mods)) + load LoadAllTargets + allMods <- getModuleGraph + selectedMod <- getModSummary (GHC.mkModuleName target) + let otherModules = filter (not . (\ms -> ms_mod ms == ms_mod selectedMod && ms_hsc_src ms == ms_hsc_src selectedMod)) allMods + targetMod <- parseTyped selectedMod + otherMods <- mapM parseTyped otherModules + res <- performCommand (readCommand command) + (SourceFileKey NormalHs target, targetMod) (zip (map keyFromMS otherModules) otherMods) + return $ (\case Right r -> Right $ (map (\case ContentChanged (n,m) -> (n ^. sfkModuleName, Just $ prettyPrint m) + ModuleCreated n m _ -> (n, Just $ prettyPrint m) + ModuleRemoved m -> (m, Nothing) + )) r + Left l -> Left l) + $ res type ParsedModule = Ann AST.UModule (Dom RdrName) SrcTemplateStage parseAST :: ModSummary -> Ghc ParsedModule parseAST modSum = do - p <- parseModule modSum + let compExts = extensionFlags $ ms_hspp_opts modSum + hasStaticFlags = fromEnum StaticPointers `member` compExts + ms = if hasStaticFlags then forceAsmGen modSum else modSum + p <- parseModule ms let annots = pm_annotations p srcBuffer = fromJust $ ms_hspp_buf $ pm_mod_summary p prepareAST srcBuffer . placeComments (snd annots) - <$> (runTrf (fst annots) (getPragmaComments $ snd annots) $ trfModule modSum $ pm_parsed_source p) + <$> (runTrf (fst annots) (getPragmaComments $ snd annots) $ trfModule ms $ pm_parsed_source p) type RenamedModule = Ann AST.UModule (Dom GHC.Name) SrcTemplateStage parseRenamed :: ModSummary -> Ghc RenamedModule parseRenamed modSum = do - p <- parseModule modSum + let compExts = extensionFlags $ ms_hspp_opts modSum + hasStaticFlags = fromEnum StaticPointers `member` compExts + ms = if hasStaticFlags then forceAsmGen modSum else modSum + p <- parseModule ms tc <- typecheckModule p let annots = pm_annotations p srcBuffer = fromJust $ ms_hspp_buf $ pm_mod_summary p prepareAST srcBuffer . placeComments (getNormalComments $ snd annots) - <$> (do parseTrf <- runTrf (fst annots) (getPragmaComments $ snd annots) $ trfModule modSum (pm_parsed_source p) + <$> (do parseTrf <- runTrf (fst annots) (getPragmaComments $ snd annots) $ trfModule ms (pm_parsed_source p) runTrf (fst annots) (getPragmaComments $ snd annots) - $ trfModuleRename modSum parseTrf + $ trfModuleRename ms parseTrf (fromJust $ tm_renamed_source tc) (pm_parsed_source p)) performRefactor :: String -> FilePath -> [String] -> String -> IO (Either String String) performRefactor command workingDir flags target = runGhc (Just libdir) $ do - initGhcFlags useFlags flags - useDirs [workingDir] ((\case Right r -> Right (newContent r); Left l -> Left l) <$> (refact =<< parseTyped =<< loadModule workingDir target)) - where refact m = performCommand (readCommand (toFileName workingDir target) command) (target,m) [] + where refact m = performCommand (readCommand command) (SourceFileKey NormalHs target,m) [] newContent (ContentChanged (_, newContent) : ress) = prettyPrint newContent + newContent ((ModuleCreated _ newContent _) : ress) = prettyPrint newContent newContent (_ : ress) = newContent ress - --- tests for ast-gen - -genTests :: [Test] -genTests = testBase ++ map makeGenTest testExprs ++ map makeGenTest testPatterns ++ map makeGenTest testType - ++ map makeGenTest testBinds ++ map makeGenTest testDecls ++ map makeGenTest testModules - -makeGenTest :: SourceInfoTraversal elem => (String, Ann elem dom SrcTemplateStage) -> Test -makeGenTest (expected, ast) = TestLabel expected $ TestCase $ assertEqual "The generated AST is not what is expected" expected (prettyPrint ast) - -testBase - = [ makeGenTest ("A.b", mkNormalName $ mkQualifiedName ["A"] "b") - , makeGenTest ("A.+", mkQualOp ["A"] "+") - , makeGenTest ("`mod`", mkBacktickOp [] "mod") - , makeGenTest ("(+)", mkParenName $ mkSimpleName "+") - ] - -testExprs - = [ ("a + 3", mkInfixApp (mkVar (mkName "a")) (mkUnqualOp "+") (mkLit $ mkIntLit 3)) - , ("(\"xx\"++)", mkLeftSection (mkLit (mkStringLit "xx")) (mkUnqualOp "++")) - , ("(1, [2, 3])", mkTuple [ mkLit (mkIntLit 1), mkList [ mkLit (mkIntLit 2), mkLit (mkIntLit 3) ] ]) - , ("P { x = 1 }", mkRecCon (mkName "P") [ mkFieldUpdate (mkName "x") (mkLit $ mkIntLit 1) ]) - , ("if f a then x else y", mkIf (mkApp (mkVar $ mkName "f") (mkVar $ mkName "a")) (mkVar $ mkName "x") (mkVar $ mkName "y")) - , ("let nat = [0..] in !z", mkLet [mkLocalValBind $ mkSimpleBind' (mkName "nat") (mkEnum (mkLit (mkIntLit 0)) Nothing Nothing)] - (mkPrefixApp (mkUnqualOp "!") (mkVar $ mkName "z")) ) - , ( "case x of Just y -> y\n" - ++ " Nothing -> 0", mkCase (mkVar (mkName "x")) [ mkAlt (mkAppPat (mkName "Just") [mkVarPat (mkName "y")]) (mkCaseRhs $ mkVar (mkName "y")) Nothing - , mkAlt (mkVarPat $ mkName "Nothing") (mkCaseRhs $ mkLit $ mkIntLit 0) Nothing - ]) - , ( "if | x > y -> x\n" - ++ " | otherwise -> y", mkMultiIf [ mkGuardedCaseRhs [mkGuardCheck $ mkInfixApp (mkVar (mkName "x")) (mkUnqualOp ">") (mkVar (mkName "y"))] (mkVar (mkName "x")) - , mkGuardedCaseRhs [mkGuardCheck $ mkVar (mkName "otherwise")] (mkVar (mkName "y")) - ]) - , ( "do x <- a\n" - ++ " return x", mkDoBlock [ G.mkBindStmt (mkVarPat (mkName "x")) (mkVar (mkName "a")) - , mkExprStmt (mkApp (mkVar $ mkName "return") (mkVar $ mkName "x")) - ]) - ] - -testPatterns - = [ ("~[0, a]", mkIrrefutablePat $ mkListPat [ mkLitPat (mkIntLit 0), mkVarPat (mkName "a") ]) - , ("p@Point{ x = 1 }", mkAsPat (mkName "p") $ mkRecPat (mkName "Point") [ mkPatternField (mkName "x") (mkLitPat (mkIntLit 1)) ]) - , ("!(_, f -> 3)", mkBangPat $ mkTuplePat [mkWildPat, mkViewPat (mkVar $ mkName "f") (mkLitPat (mkIntLit 3))]) - ] - -testType - = [ ("forall x . Eq x => x -> ()", mkForallType [mkTypeVar (mkName "x")] - $ mkCtxType (mkContextOne (mkClassAssert (mkName "Eq") [mkVarType (mkName "x")])) - $ mkFunctionType (mkVarType (mkName "x")) (mkVarType (mkName "()"))) - , ("(A :+: B) (x, x)", mkTypeApp (mkParenType $ mkInfixTypeApp (mkVarType (mkName "A")) (mkUnqualOp ":+:") (mkVarType (mkName "B"))) - (mkTupleType [ mkVarType (mkName "x"), mkVarType (mkName "x") ])) - ] - -testBinds - = [( "x = (a, b) where a = 3\n" - ++ " b = 4", mkSimpleBind (mkVarPat (mkName "x")) (mkUnguardedRhs (mkTuple [(mkVar (mkName "a")), (mkVar (mkName "b"))])) - (Just $ mkLocalBinds' [ mkLocalValBind $ mkSimpleBind' (mkName "a") (mkLit $ mkIntLit 3) - , mkLocalValBind $ mkSimpleBind' (mkName "b") (mkLit $ mkIntLit 4) - ]) ) - ,( "f i 0 = i\n" - ++ "f i x = x", mkFunctionBind' (mkName "f") [ ([mkVarPat $ mkName "i", mkLitPat $ mkIntLit 0], mkVar $ mkName "i") - , ([mkVarPat $ mkName "i", mkVarPat $ mkName "x"], mkVar $ mkName "x") - ]) - ] - -testDecls - = [ ("id :: a -> a", mkTypeSigDecl $ mkTypeSignature (mkName "id") (mkFunctionType (mkVarType (mkName "a")) (mkVarType (mkName "a")))) - , ("id x = x", mkValueBinding $ mkFunctionBind' (mkName "id") [([mkVarPat $ mkName "x"], mkVar $ mkName "x")]) - , ("data A a = A a deriving Show", mkDataDecl mkDataKeyword Nothing (mkDeclHeadApp (mkNameDeclHead (mkName "A")) (mkTypeVar (mkName "a"))) - [mkConDecl (mkName "A") [mkVarType (mkName "a")]] (Just $ mkDeriving [mkInstanceHead (mkName "Show")])) - , ("data A = A { x :: Int }", mkDataDecl mkDataKeyword Nothing (mkNameDeclHead (mkName "A")) - [mkRecordConDecl (mkName "A") [mkFieldDecl [mkName "x"] (mkVarType (mkName "Int"))]] Nothing) - , ( "class A t => C t where f :: t\n" - ++ " type T t :: *" - , mkClassDecl (Just $ mkContextOne (mkClassAssert (mkName "A") [mkVarType (mkName "t")])) - (mkDeclHeadApp (mkNameDeclHead (mkName "C")) (mkTypeVar (mkName "t"))) [] - (Just $ mkClassBody [ mkClassElemSig $ mkTypeSignature (mkName "f") (mkVarType (mkName "t")) - , mkClassElemTypeFam (mkDeclHeadApp (mkNameDeclHead (mkName "T")) (mkTypeVar (mkName "t"))) - (Just $ mkTypeFamilyKindSpec $ mkKindConstraint $ mkKindStar) - ]) - ) - , ("instance C Int where f = 0", mkInstanceDecl Nothing (mkInstanceRule Nothing $ mkAppInstanceHead (mkInstanceHead $ mkName "C") (mkVarType (mkName "Int"))) - (Just $ mkInstanceBody [mkInstanceBind $ mkSimpleBind' (mkName "f") (mkLit $ mkIntLit 0)])) - , ("infixl 6 +", mkFixityDecl $ mkInfixL 6 (mkUnqualOp "+")) - ] - -testModules - = [ ("", G.mkModule [] Nothing [] []) - , ("module Test(x, A(a), B(..)) where", G.mkModule [] (Just $ mkModuleHead (G.mkModuleName "Test") (Just $ mkExportSpecs [ - mkExportSpec $ mkIESpec (mkName "x") Nothing - , mkExportSpec $ mkIESpec (mkName "A") (Just $ mkSubList [mkName "a"]) - , mkExportSpec $ mkIESpec (mkName "B") (Just mkSubAll) - ]) Nothing) [] []) - , ("\nimport qualified A\n" - ++ "import B as BB(x)\n" - ++ "import B hiding (x)", G.mkModule [] Nothing [ mkImportDecl False True False Nothing (G.mkModuleName "A") Nothing Nothing - , mkImportDecl False False False Nothing (G.mkModuleName "B") (Just $ G.mkModuleName "BB") (Just $ mkImportSpecList [mkIESpec (mkName "x") Nothing]) - , mkImportDecl False False False Nothing (G.mkModuleName "B") Nothing (Just $ mkImportHidingList [mkIESpec (mkName "x") Nothing]) - ] []) - ]