smuggler2 0.3.3.2 → 0.3.4.1
raw patch · 12 files changed
+130/−76 lines, 12 filesdep +ghc-bootPVP ok
version bump matches the API change (PVP)
Dependencies added: ghc-boot
API changes (from Hackage documentation)
Files
- CHANGELOG.md +3/−0
- Makefile +2/−0
- README.md +25/−18
- TODO.md +11/−7
- smuggler2.cabal +2/−1
- src/Smuggler2/Anns.hs +6/−2
- src/Smuggler2/Exports.hs +4/−3
- src/Smuggler2/Imports.hs +6/−3
- src/Smuggler2/Options.hs +2/−3
- src/Smuggler2/Parser.hs +3/−4
- src/Smuggler2/Plugin.hs +43/−35
- test/tests/CPP.hs +23/−0
CHANGELOG.md view
@@ -5,6 +5,9 @@ `smuggler2` uses [PVP Versioning][1]. The change log is available [on GitHub][2]. +## [0.3.4.1]: -- 9 June 2020+- Place output from CPP and literate Haskell sources into new files+ ## [0.3.3.2]: -- 8 June 2020 - pattern synonyms and type families are handled - ghc-smuggler2 wrapper for ghc
Makefile view
@@ -27,6 +27,8 @@ doc: cabal haddock+ cabal v2-haddock --haddock-for-hackage --enable-doc+# cabal upload -d --publish dist-newstyle/smuggler2-0.3.x.y-docs.tar.gz hlint: hlint src test/Test.hs
README.md view
@@ -42,7 +42,7 @@ common smuggler-options if flag(smuggler2) ghc-options: -fplugin=Smuggler2.Plugin- build-depends: smuggler2 >= 0.3+ build-depends: smuggler2 >= 0.3 && < 0.4 ``` and then `import: smuggler-options` in the appropriate `library` or `executable`@@ -76,12 +76,14 @@ ```bash $ cabal build -with-compiler=ghc-smuggler2 ```+ or just+ ```bash $ cabal build -w ghc-smuggler2 ``` -### Options+## Options `Smuggler2` has several (case-insensitive) options, which can be set by adding `-fplugin-opt=Smuggler2.Plugin:` flags to your `ghc-options`@@ -123,16 +125,15 @@ $ ghcid --command='cabal repl' ``` - ## Caveats -- Because `cabal` and `ghc` don't have full support for distinguishing dependent- packages from plug-ins you will probably want to ensure that the build- dependencies for your project are installed into your local package db first,- before enabling sumuggler, otherwise they will all be processed by it too,- as your project builds, which should do no harm, but will increase your build time.+Because `cabal` and `ghc` don't have full support for distinguishing dependent+packages from plug-ins you will probably want to ensure that the build+dependencies for your project are installed into your local package db first,+before enabling sumuggler, otherwise they will all be processed by it too,+as your project builds, which should do no harm, but will increase your build time. -`Smugggler2` is robust -- it can chew through the+`Smuggler2` is robust -- it can chew through the [Agda](https://github.com/agda/agda) codebase of over 370 modules with complex interdependencies and be tripped over by only @@ -141,16 +142,20 @@ - and a couple of imports where both qualifed and unqualifed version of the module are imported and there are references to both qualified and unqualifed version of the same names+- some qualified record fields are overlooked But there are some caveats, most of which are either easy enough to work around (and still offer the benefit of a great reduction in keyboard work): - `Smuggler2` rewrites the existing imports, rather than attempting to prune them. (This is a more aggressive approach than `smuggler` which focuses on- removing redundant imports.) It has advantages and disadvantages. The- advantage is that a minimal set of imports is generated in a reproducable- format. The disdvantage is that imports may be reordered, comments and- blankdropped, external imports mixed with external, etc.+ removing redundant imports.) It has advantages and disadvantages.+ The advantage is that a minimal set of imports is generated in a reproducable+ format. So you can just import a library without specifying any specific+ imports and `Smuggler2` will add an explict list of things that are+ used from it. This can be a useful check and better document your modules.+ The disdvantage is that imports may be reordered, comments and+ blank lines dropped, external imports mixed with external, etc. - By default `Smuggler2` does not remove imports completely because an import may be being used to only import instances of typeclasses, So it will leave@@ -167,7 +172,8 @@ - CPP files will not be processed correctly: the imports will be generated for current CPP settings and any CPP annotations in the import block will be discarded. This may be a particular problem if you are writing code for- several generations of `ghc` and `base` for example.+ several generations of `ghc` and `base` for example. Nevetheless, `Smuggler2`+ will generate a new CPP preprocessed output file with a `-cpp` suffix. [retrie](https://github.com/facebookincubator/retrie/blob/master/Retrie/CPP.hs) solves this problem generating all possible versions of the module (exponential in the number of `#if` directives), operating on each version@@ -183,15 +189,16 @@ - Multiple separate import lines referring to the same library are not consolidated -- Literate Haskell `lhs` files are not supported+- Literate Haskell `.lhs` files will procssed into ordinary haskell files a+ `-lhs` suffix. -- `hiding` clauses may not be properly analysed. So hiding things that are not+* `hiding` clauses may not be properly analysed. So hiding things that are not used may not be spotted. -- The test suite does not seem to run reliably on Windows. This is probably more+* The test suite does not seem to run reliably on Windows. This is probably more of an issue with the way that the tests are run, than `Smuggler2` itself. -- Currently `cabal` does not have a particular way of specifying plugins. (See,+* Currently `cabal` does not have a particular way of specifying plugins. (See, eg, https://gitlab.haskell.org/ghc/ghc/issues/11244 and https://github.com/haskell/cabal/issues/2965) which would allow cleaner separation of user code and plugin-code
TODO.md view
@@ -1,12 +1,13 @@ # Things to be done -- [ ] does hiding work?+- [ ] Does hiding work? No, as ghc doesn't check them -- [ ] does CPP work? (should do)+- [X] Does CPP work? Yes, but the mark is dropped, so put output into a new+ file. Similarly for literate Haskell. -- [ ] Script to generate new test (from template?)+- [x] Script to generate new test (from template?) Not worth the effort -- [ ] get tasty-golden to delete successful results and update tests where+- [X] get tasty-golden to delete successful results and update tests where smuggler makes no changes - [ ] refactor the much more sophisticated https://github.com/ddssff/refact-global-hse@@ -17,16 +18,19 @@ it creates race conditions because both cases will generate the same minimum imprts dump file -- [ ] Do a better job of preserving imported pattern annotation.+- [X] Do a better job of preserving imported pattern annotation. -- [ ] Check that type imports / exports work, Type Operators, +- [X] Check that type imports / exports work, Type Operators, - [X] Running test suite on both an imported module and the module that imports it creates race conditions because both cases will generate the same minimum imports dump file -- [ ] User ghc environment files instead of `cabal exec` to launch tests+- [x] User ghc environment files instead of `cabal exec` to launch tests. But+ these are invisible and so likely to trip you up - [ ] Use NamedFieldPuns / RecordWildCards for Options - [ ] Check that an export does not need to be qualified++- [ ]- Figure out fhy github workflow uses ghc 8.10.1 when it should be running 8.8.3
smuggler2.cabal view
@@ -1,6 +1,6 @@ cabal-version: 3.0 name: smuggler2-version: 0.3.3.2+version: 0.3.4.1 synopsis: GHC Source Plugin that helps to minimise imports and generate explicit exports @@ -109,6 +109,7 @@ , directory ^>=1.3.3 , filepath ^>=1.4.2 , ghc >=8.6.5 && <8.11+ , ghc-boot >=8.6.5 && <8.11 , ghc-exactprint ^>=0.6.3 , syb ^>=0.7.1
src/Smuggler2/Anns.hs view
@@ -15,8 +15,12 @@ import Data.Maybe (fromMaybe) import GHC (AnnKeywordId (AnnCloseP, AnnOpenP)) import GhcPlugins (GenLocated (L), Located)-import Language.Haskell.GHC.ExactPrint (Annotation (annEntryDelta, annPriorComments, annsDP),- TransformT, modifyAnnsT, uniqueSrcSpanT)+import Language.Haskell.GHC.ExactPrint+ ( Annotation (annEntryDelta, annPriorComments, annsDP),+ TransformT,+ modifyAnnsT,+ uniqueSrcSpanT,+ ) import Language.Haskell.GHC.ExactPrint.Types (DeltaPos (..), KeywordId (G), annNone, mkAnnKey) -- Inspired by retrie
src/Smuggler2/Exports.hs view
@@ -1,8 +1,9 @@ -- | -- Description: Utility functions for generating an export list ast and--- associated Anns. It's a bit fiddlier than it could be because ghc's--- functions for producing exportable things generates @AvailInfo@ from which we--- need to reconstitue @IEWrappedName@ and then @IE@+-- associated Anns. It's a bit fiddlier than it could be because ghc's+-- functions for producing exportable things generates @AvailInfo@ from which we+-- need to reconstitue @IEWrappedName@ and then @IE@+ module Smuggler2.Exports ( mkExportAnnT )
src/Smuggler2/Imports.hs view
@@ -1,7 +1,8 @@ {-# LANGUAGE CPP #-}--- | Description: A replacement for 'RnNames.getMinimalImports' that attempts--- to handle patterns and types, which is not done correctly in GHC 8.10.1 and--- earlier.+-- |+-- Description: A replacement for 'RnNames.getMinimalImports' that attempts+-- to handle patterns and types, which is not done correctly in GHC 8.10.1 and+-- earlier. module Smuggler2.Imports (getMinimalImports) where import Avail ( AvailInfo(..) )@@ -20,7 +21,9 @@ import LoadIface ( loadSrcInterface ) import Name ( HasOccName(..), isDataOcc, isSymOcc, isTcOcc ) import Outputable ( Outputable(ppr), text, (<+>) )+#if MIN_VERSION_GLASGOW_HASKELL(8,8,0,0) import RdrName ( gresToAvailInfo )+#endif import RnNames ( ImportDeclUsage ) import SrcLoc ( GenLocated(L), Located, noLoc ) import TcRnMonad ( RnM )
src/Smuggler2/Options.hs view
@@ -1,7 +1,6 @@-{- |- Description: handling of command line options+-- |+-- Description: handling of command line options - -} module Smuggler2.Options ( Options (..), parseCommandLineOptions,
src/Smuggler2/Parser.hs view
@@ -1,7 +1,6 @@-{-|- Description: provides a wrapper around the 'ghc-exactprint' parser- -} {-# LANGUAGE CPP #-}+-- |+-- Description: provides a wrapper around the 'ghc-exactprint' parser module Smuggler2.Parser ( runParser,@@ -14,7 +13,7 @@ import Language.Haskell.GHC.ExactPrint (Anns) import Language.Haskell.GHC.ExactPrint.Parsers (parseModuleFromStringInternal) #if MIN_VERSION_GLASGOW_HASKELL(8,10,1,0)-import ErrUtils (fatalErrorMsg, printBagOfErrors)+import ErrUtils (fatalErrorMsg, printBagOfErrors ) import Outputable (text) #else import ErrUtils (fatalErrorMsg)
src/Smuggler2/Plugin.hs view
@@ -9,9 +9,11 @@ import Avail ( AvailInfo, Avails ) import Control.Monad ( unless )+import Data.Bool ( bool ) import Data.Maybe ( fromMaybe, isNothing ) import Data.Version ( showVersion )-import DynFlags ( DynFlags(dumpDir), HasDynFlags(getDynFlags) )+import DynFlags+ ( DynFlags(dumpDir), HasDynFlags(getDynFlags), xopt ) import ErrUtils ( compilationProgressMsg, fatalErrorMsg ) import GHC ( GenLocated(L),@@ -21,11 +23,14 @@ LIE, LImportDecl, Located,- ModSummary(ms_hspp_buf, ms_hspp_file, ms_mod),+ ms_location,+ ml_hs_file,+ ModSummary(ms_hspp_buf, ms_mod), Module(moduleName), ParsedSource, moduleNameString, unLoc )+import GHC.LanguageExtensions ( Extension(Cpp) ) import GHC.IO.Encoding ( setLocaleEncoding, utf8 ) import IOEnv ( MonadIO(liftIO), readMutVar ) import Language.Haskell.GHC.ExactPrint@@ -45,11 +50,10 @@ Plugin(pluginRecompile, typeCheckResultAction), defaultPlugin, purePlugin )-import RnNames- ( ImportDeclUsage, findImportUsage )-import Smuggler2.Anns-import Smuggler2.Imports-import Smuggler2.Exports+import RnNames ( ImportDeclUsage, findImportUsage )+import Smuggler2.Anns ( mkLoc, mkParenT )+import Smuggler2.Imports ( getMinimalImports )+import Smuggler2.Exports ( mkExportAnnT ) import Smuggler2.Options ( ExportAction(AddExplicitExports, NoExportProcessing, ReplaceExports),@@ -59,7 +63,7 @@ import Smuggler2.Parser ( runParser ) import StringBuffer ( StringBuffer(StringBuffer), lexemeToString ) import System.Directory ( removeFile )-import System.FilePath ( (-<.>), (</>) )+import System.FilePath ( (-<.>), (</>), isExtensionOf, takeExtension ) import System.IO ( IOMode(WriteMode), withFile ) import TcRnExports ( exports_from_avail ) import TcRnTypes@@ -92,19 +96,20 @@ -- This ensures that the source file is not touched if there are no unused -- imports, or exports already exist and we are not replacing them let noUnusedImports = all (\(_decl, used, unused) -> not (null used) && null unused) usage+ let hasExplicitExports = case tcg_rn_exports tcEnv of- Nothing -> False -- There is not even a module header+ Nothing -> False -- There is not even a module header (Just []) -> False- (Just _) -> True+ (Just _) -> True -- ... so short circuit if: -- - we are skipping import processing or there are no unused imports, and -- - we are skipping export processing or there are explict exports and we are not replacing them -- (This is not a complete check; ideally, that the new imp/exports are -- different from the existing ones, etc)- if (importAction options == NoImportProcessing || noUnusedImports) &&- ( exportAction options == NoExportProcessing- || (hasExplicitExports && exportAction options /= ReplaceExports)- )+ if (importAction options == NoImportProcessing || noUnusedImports)+ && ( exportAction options == NoExportProcessing+ || (hasExplicitExports && exportAction options /= ReplaceExports)+ ) then return tcEnv else do dflags <- getDynFlags@@ -123,14 +128,14 @@ -- Run smuggling only for its side effects tcEnv <$ smuggling dflags minImpFilePath- where- -- Does all the work smuggling :: DynFlags -> FilePath -> RnM () smuggling dflags minImpFilePath = do-- let modulePath = ms_hspp_file modSummary+ -- The preprocessed source+ let modulePath = case ml_hs_file $ ms_location modSummary of+ Nothing -> error "smuggler2: missing source file location"+ Just loc -> loc -- Read files as UTF-8 strings (GHC accepts only ASCII or UTF-8) liftIO $ setLocaleEncoding utf8@@ -138,7 +143,7 @@ -- Get the pre-processed module source code let modFileContents = case ms_hspp_buf modSummary of -- Not clear under what circumstances this could happen- Nothing -> error $ "smuggler: missing source file: " ++ modulePath+ Nothing -> error $ "smuggler2: missing source file: " ++ modulePath Just contents -> strBufToStr contents -- Parse the whole module@@ -154,7 +159,6 @@ liftIO $ fatalErrorMsg dflags (text $ "smuggler: failed to parse minimal imports from " ++ minImpFilePath) Right (annsImpMod, L _ impMod) -> do- -- The actual minimal imports themselves, as generated by GHC let minImports = hsmodImports impMod @@ -170,19 +174,27 @@ replaceImports annsImpMod minImports astHsMod >>= addExplicitExports exports + -- Generate new file extension+ -- Was the CPP used?+ let usedCpp = bool "" "-cpp" (xopt Cpp dflags)+ -- Was the source literate Haskell+ let wasLhs = bool "" "-lhs" (isExtensionOf "lhs" modulePath)+ -- prefix any user-supplied options+ let ext =+ fromMaybe (takeExtension modulePath) (newExtension options)+ ++ usedCpp+ ++ wasLhs+ -- Print the result let newContent = exactPrint astHsMod' annsHsMod'- liftIO $ case newExtension options of- Nothing -> writeFile modulePath newContent- Just ext -> writeFile (modulePath -<.> ext) newContent+ liftIO $ writeFile (modulePath -<.> ext) newContent - -- Clean up: delete the GHC-generated imports file+ -- Clean up: delete the GHC-generated imports file liftIO $ removeFile minImpFilePath- where -- Generates the things that would be exportabe if there were no- -- explict export header, so suitable for replacing one+ -- explicit export header, so suitable for replacing one exportable :: RnM [AvailInfo] exportable = do let rdr_env = tcg_rdr_env tcEnv@@ -191,7 +203,7 @@ exports <- exports_from_avail Nothing rdr_env imports this_mod return (snd exports) - -- | Replace a target module's imports+ -- Replace a target module's imports -- See <https://github.com/facebookincubator/retrie/blob/master/Retrie/CPP.hs> replaceImports :: Monad m =>@@ -214,7 +226,7 @@ unless (null imps) $ setEntryDPT (head imps) (DP (2, 0)) return $ L l m {hsmodImports = imps} - -- | Add explict exports to the target module+ -- Add explict exports to the target module addExplicitExports :: Monad m => -- | The list of exports to be added@@ -235,14 +247,13 @@ where currentExplicitExports :: Maybe (Located [LIE GhcPs]) currentExplicitExports = hsmodExports hsMod- -- This does all the export replacement work result :: Monad m => TransformT m ParsedSource result | null exports = return t -- there is nothing exportable | otherwise = do -- Generate the exports list- exportsList <- mapM mkExportAnnT exports+ exportsList <- mapM mkExportAnnT exports -- add commas in between and parens around mapM_ addTrailingCommaT (init exportsList) lExportsList <- mkLoc exportsList >>= mkParenT unLoc@@ -251,7 +262,7 @@ -- annotations in the current transformation return $ L astLoc hsMod {hsmodExports = Just lExportsList} - -- | This version of the GHC function ignores implicit imports, as they+ -- This version of the GHC function ignores implicit imports, as they -- cannot be parsed back in. (There is an extraneous (implicit)) -- It also provides for leaving out instance-only imports (eg, Data.List() ) printMinimalImports' :: DynFlags -> FilePath -> [ImportDeclUsage] -> RnM ()@@ -273,15 +284,12 @@ where notImplicit :: ImportDecl pass -> Bool notImplicit = not . ideclImplicit- notInstancesOnly :: ImportDecl pass -> Bool notInstancesOnly i = case ideclHiding i of Just (False, L _ []) -> False- _ -> True-+ _ -> True keepInstanceOnlyImports :: Bool keepInstanceOnlyImports = importAction options /= MinimiseImports- letThrough :: ImportDecl pass -> Bool letThrough i = notImplicit i && (keepInstanceOnlyImports || notInstancesOnly i)
+ test/tests/CPP.hs view
@@ -0,0 +1,23 @@+{-# LANGUAGE CPP #-}+module CPP where+#if !(MIN_VERSION_base(4,8,0))+import Data.Monoid+#endif+#if !(MIN_VERSION_base(4,11,0))+import Data.Semigroup+#endif++#if !(MIN_VERSION_base(4,5,0)) && !(MIN_VERSION_base(4,9,0))++infixr 6 <>++-- | An infix synonym for 'mappend'.+--+-- /Since: 4.5.0.0/+(<>) :: Monoid m => m -> m -> m+(<>) = mappend+{-# INLINE (<>) #-}++#endif++str = "abc" <> "def"