imp 1.0.1.0 → 1.0.2.0
raw patch · 11 files changed
+165/−10 lines, 11 filesdep +Cabal-syntaxPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: Cabal-syntax
API changes (from Hackage documentation)
- Imp.Type.Context: newtype Context
+ Imp.Exception.InvalidPackage: InvalidPackage :: String -> InvalidPackage
+ Imp.Exception.InvalidPackage: instance GHC.Classes.Eq Imp.Exception.InvalidPackage.InvalidPackage
+ Imp.Exception.InvalidPackage: instance GHC.Exception.Type.Exception Imp.Exception.InvalidPackage.InvalidPackage
+ Imp.Exception.InvalidPackage: instance GHC.Show.Show Imp.Exception.InvalidPackage.InvalidPackage
+ Imp.Exception.InvalidPackage: new :: String -> InvalidPackage
+ Imp.Exception.InvalidPackage: newtype InvalidPackage
+ Imp.Exception.InvalidPackageName: InvalidPackageName :: String -> InvalidPackageName
+ Imp.Exception.InvalidPackageName: instance GHC.Classes.Eq Imp.Exception.InvalidPackageName.InvalidPackageName
+ Imp.Exception.InvalidPackageName: instance GHC.Exception.Type.Exception Imp.Exception.InvalidPackageName.InvalidPackageName
+ Imp.Exception.InvalidPackageName: instance GHC.Show.Show Imp.Exception.InvalidPackageName.InvalidPackageName
+ Imp.Exception.InvalidPackageName: new :: String -> InvalidPackageName
+ Imp.Exception.InvalidPackageName: newtype InvalidPackageName
+ Imp.Type.Config: [packages] :: Config -> [Package]
+ Imp.Type.Context: [packages] :: Context -> Map Target PackageName
+ Imp.Type.Context: data Context
+ Imp.Type.Flag: Package :: String -> Flag
+ Imp.Type.Package: Package :: Target -> PackageName -> Package
+ Imp.Type.Package: [module_] :: Package -> Target
+ Imp.Type.Package: [package] :: Package -> PackageName
+ Imp.Type.Package: data Package
+ Imp.Type.Package: fromString :: MonadThrow m => String -> m Package
+ Imp.Type.Package: instance GHC.Classes.Eq Imp.Type.Package.Package
+ Imp.Type.Package: instance GHC.Show.Show Imp.Type.Package.Package
+ Imp.Type.Package: toMap :: [Package] -> Map Target PackageName
+ Imp.Type.PackageName: PackageName :: PackageName -> PackageName
+ Imp.Type.PackageName: fromCabal :: PackageName -> PackageName
+ Imp.Type.PackageName: fromString :: MonadThrow m => String -> m PackageName
+ Imp.Type.PackageName: instance GHC.Classes.Eq Imp.Type.PackageName.PackageName
+ Imp.Type.PackageName: instance GHC.Show.Show Imp.Type.PackageName.PackageName
+ Imp.Type.PackageName: newtype PackageName
+ Imp.Type.PackageName: toCabal :: PackageName -> PackageName
+ Imp.Type.PackageName: toStringLiteral :: PackageName -> StringLiteral
- Imp: createImport :: Map Target Source -> ModuleName -> Maybe (ImportDecl GhcPs)
+ Imp: createImport :: Map Target Source -> Map Target PackageName -> ModuleName -> Maybe (ImportDecl GhcPs)
- Imp: updateImports :: ModuleName -> Map Target Source -> Map ModuleName SrcSpanAnnN -> [LImportDecl GhcPs] -> [LImportDecl GhcPs]
+ Imp: updateImports :: ModuleName -> Map Target Source -> Map Target PackageName -> Map ModuleName SrcSpanAnnN -> [LImportDecl GhcPs] -> [LImportDecl GhcPs]
- Imp.Type.Config: Config :: [Alias] -> Bool -> Bool -> Config
+ Imp.Type.Config: Config :: [Alias] -> Bool -> [Package] -> Bool -> Config
- Imp.Type.Context: Context :: Map Target Source -> Context
+ Imp.Type.Context: Context :: Map Target Source -> Map Target PackageName -> Context
Files
- README.md +23/−0
- imp.cabal +6/−1
- source/library/Imp.hs +13/−5
- source/library/Imp/Exception/InvalidPackage.hs +13/−0
- source/library/Imp/Exception/InvalidPackageName.hs +13/−0
- source/library/Imp/Type/Config.hs +6/−0
- source/library/Imp/Type/Context.hs +7/−3
- source/library/Imp/Type/Flag.hs +10/−1
- source/library/Imp/Type/Package.hs +25/−0
- source/library/Imp/Type/PackageName.hs +31/−0
- source/test-suite/Main.hs +18/−0
README.md view
@@ -117,6 +117,29 @@ -- and so on ... ``` +## Package Imports++It's possible that the same module name can be defined in two different+packages. In normal Haskell code, you can disambiguate using the+`PackageImports` language extension. To do the same with Imp, use the+`--package=MODULE:PACKAGE` option. For example, consider the following module:++``` hs+{-# LANGUAGE PackageImports #-}+{-# OPTIONS_GHC+ -fplugin=Imp+ -fplugin-opt=Imp:--package=Data.SemVer:semver #-}+main = print Data.SemVer.initial+```++That will produce the following output:++``` hs+{-# LANGUAGE PackageImports #-}+import qualified "semver" Data.SemVer+main = print Data.SemVer.initial+```+ ## Limitations Due to limitations in how GHC plugins work, Imp cannot be used to automatically
imp.cabal view
@@ -1,6 +1,6 @@ cabal-version: 2.2 name: imp-version: 1.0.1.0+version: 1.0.2.0 synopsis: A GHC plugin for automatically importing modules. description: Imp is a GHC plugin for automatically importing modules. category: Plugin@@ -46,6 +46,7 @@ import: library autogen-modules: Paths_imp build-depends:+ Cabal-syntax ^>=3.6.0.0 || ^>=3.8.1.0 || ^>=3.10.1.0, containers ^>=0.6.7, exceptions ^>=0.10.5, ghc ^>=9.4.1 || ^>=9.6.1 || ^>=9.8.1,@@ -57,6 +58,8 @@ Imp.Exception.InvalidAlias Imp.Exception.InvalidModuleName Imp.Exception.InvalidOption+ Imp.Exception.InvalidPackage+ Imp.Exception.InvalidPackageName Imp.Exception.ShowHelp Imp.Exception.ShowVersion Imp.Exception.UnexpectedArgument@@ -75,6 +78,8 @@ Imp.Type.Config Imp.Type.Context Imp.Type.Flag+ Imp.Type.Package+ Imp.Type.PackageName Imp.Type.Source Imp.Type.Target
source/library/Imp.hs view
@@ -12,6 +12,7 @@ import qualified Data.Set as Set import qualified GHC.Hs as Hs import qualified GHC.Plugins as Plugin+import qualified GHC.Types.PkgQual as PkgQual import qualified Imp.Exception.ShowHelp as ShowHelp import qualified Imp.Exception.ShowVersion as ShowVersion import qualified Imp.Extra.Exception as Exception@@ -25,6 +26,7 @@ import qualified Imp.Type.Config as Config import qualified Imp.Type.Context as Context import qualified Imp.Type.Flag as Flag+import qualified Imp.Type.PackageName as PackageName import qualified Imp.Type.Source as Source import qualified Imp.Type.Target as Target import qualified System.Exit as Exit@@ -73,6 +75,7 @@ config <- Config.fromFlags flags context <- Context.fromConfig config let aliases = Context.aliases context+ packages = Context.packages context implicits = Set.map Target.toModuleName . Map.keysSet@@ -86,7 +89,7 @@ StateT.runState (Located.overValue (HsModule.overDecls $ overData $ updateQualifiedIdentifiers this implicits imports) lHsModule) Map.empty- pure $ fmap (HsModule.overImports $ updateImports this aliases moduleNames) newLHsModule+ pure $ fmap (HsModule.overImports $ updateImports this aliases packages moduleNames) newLHsModule updateQualifiedIdentifiers :: (Data.Data a) =>@@ -121,19 +124,21 @@ updateImports :: Plugin.ModuleName -> Map.Map Target.Target Source.Source ->+ Map.Map Target.Target PackageName.PackageName -> Map.Map Plugin.ModuleName Hs.SrcSpanAnnN -> [Hs.LImportDecl Hs.GhcPs] -> [Hs.LImportDecl Hs.GhcPs]-updateImports this aliases want imports =+updateImports this aliases packages want imports = let have = Set.insert this . Set.fromList $ fmap (ImportDecl.toModuleName . Plugin.unLoc) imports need = Map.toList $ Map.withoutKeys want have- in imports <> Maybe.mapMaybe (\(m, l) -> Plugin.L (Hs.na2la l) <$> createImport aliases m) need+ in imports <> Maybe.mapMaybe (\(m, l) -> Plugin.L (Hs.na2la l) <$> createImport aliases packages m) need createImport :: Map.Map Target.Target Source.Source ->+ Map.Map Target.Target PackageName.PackageName -> Plugin.ModuleName -> Maybe (Hs.ImportDecl Hs.GhcPs)-createImport aliases target = do+createImport aliases packages target = do source <- case Map.lookup (Target.fromModuleName target) aliases of Nothing -> Just target@@ -145,5 +150,8 @@ { Hs.ideclAs = if source == target then Nothing- else Just $ Hs.noLocA target+ else Just $ Hs.noLocA target,+ Hs.ideclPkgQual =+ maybe PkgQual.NoRawPkgQual (PkgQual.RawPkgQual . PackageName.toStringLiteral) $+ Map.lookup (Target.fromModuleName target) packages }
+ source/library/Imp/Exception/InvalidPackage.hs view
@@ -0,0 +1,13 @@+module Imp.Exception.InvalidPackage where++import qualified Control.Monad.Catch as Exception++newtype InvalidPackage+ = InvalidPackage String+ deriving (Eq, Show)++instance Exception.Exception InvalidPackage where+ displayException (InvalidPackage x) = "invalid package: " <> show x++new :: String -> InvalidPackage+new = InvalidPackage
+ source/library/Imp/Exception/InvalidPackageName.hs view
@@ -0,0 +1,13 @@+module Imp.Exception.InvalidPackageName where++import qualified Control.Monad.Catch as Exception++newtype InvalidPackageName+ = InvalidPackageName String+ deriving (Eq, Show)++instance Exception.Exception InvalidPackageName where+ displayException (InvalidPackageName x) = "invalid package name: " <> show x++new :: String -> InvalidPackageName+new = InvalidPackageName
source/library/Imp/Type/Config.hs view
@@ -4,10 +4,12 @@ import qualified Control.Monad.Catch as Exception import qualified Imp.Type.Alias as Alias import qualified Imp.Type.Flag as Flag+import qualified Imp.Type.Package as Package data Config = Config { aliases :: [Alias.Alias], help :: Bool,+ packages :: [Package.Package], version :: Bool } deriving (Eq, Show)@@ -17,6 +19,7 @@ Config { aliases = [], help = False,+ packages = [], version = False } @@ -29,4 +32,7 @@ alias <- Alias.fromString string pure config {aliases = alias : aliases config} Flag.Help bool -> pure config {help = bool}+ Flag.Package string -> do+ package <- Package.fromString string+ pure config {packages = package : packages config} Flag.Version bool -> pure config {version = bool}
source/library/Imp/Type/Context.hs view
@@ -7,11 +7,14 @@ import qualified Imp.Exception.ShowVersion as ShowVersion import qualified Imp.Type.Alias as Alias import qualified Imp.Type.Config as Config+import qualified Imp.Type.Package as Package+import qualified Imp.Type.PackageName as PackageName import qualified Imp.Type.Source as Source import qualified Imp.Type.Target as Target -newtype Context = Context- { aliases :: Map.Map Target.Target Source.Source+data Context = Context+ { aliases :: Map.Map Target.Target Source.Source,+ packages :: Map.Map Target.Target PackageName.PackageName } deriving (Eq, Show) @@ -21,5 +24,6 @@ Monad.when (Config.version config) $ Exception.throwM ShowVersion.new pure Context- { aliases = Alias.toMap $ Config.aliases config+ { aliases = Alias.toMap $ Config.aliases config,+ packages = Package.toMap $ Config.packages config }
source/library/Imp/Type/Flag.hs view
@@ -9,6 +9,7 @@ data Flag = Alias String | Help Bool+ | Package String | Version Bool deriving (Eq, Show) @@ -40,7 +41,15 @@ (GetOpt.ReqArg Alias "SOURCE:TARGET") "Adds a new alias, allowing TARGET to be used in place of SOURCE. \ \For example `--alias=Data.String:String` allows `String.words` to mean `Data.String.words`. \- \Later aliases will overwrite earlier ones."+ \Later aliases will overwrite earlier ones.",+ GetOpt.Option+ []+ ["package"]+ (GetOpt.ReqArg Package "MODULE:PACKAGE")+ "Specifies that MODULE should be imported from PACKAGE. \+ \For example `--package=Data.Semver:semver` will import the `Data.SemVer` module from the `semver` package. \+ \Note that using this option requires you to enable the `PackageImports` language extension. \+ \Later packages will overwrite earlier ones." ] fromArguments :: (Exception.MonadThrow m) => [String] -> m [Flag]
+ source/library/Imp/Type/Package.hs view
@@ -0,0 +1,25 @@+module Imp.Type.Package where++import qualified Control.Monad as Monad+import qualified Control.Monad.Catch as Exception+import qualified Data.Map as Map+import qualified Imp.Exception.InvalidPackage as InvalidPackage+import qualified Imp.Type.PackageName as PackageName+import qualified Imp.Type.Target as Target++data Package = Package+ { module_ :: Target.Target,+ package :: PackageName.PackageName+ }+ deriving (Eq, Show)++fromString :: (Exception.MonadThrow m) => String -> m Package+fromString string = do+ let (before, after) = break (== ':') string+ Monad.when (null after) . Exception.throwM $ InvalidPackage.new string+ mdl <- Target.fromString before+ pkg <- PackageName.fromString . drop 1 $ after+ pure Package {module_ = mdl, package = pkg}++toMap :: [Package] -> Map.Map Target.Target PackageName.PackageName+toMap = Map.fromListWith (const id) . fmap (\x -> (module_ x, package x))
+ source/library/Imp/Type/PackageName.hs view
@@ -0,0 +1,31 @@+module Imp.Type.PackageName where++import qualified Control.Monad.Catch as Exception+import qualified Distribution.Parsec as Parsec+import qualified Distribution.Types.PackageName as PackageName+import qualified GHC.Data.FastString as FastString+import qualified GHC.Types.SourceText as SourceText+import qualified Imp.Exception.InvalidPackageName as InvalidPackageName++newtype PackageName+ = PackageName PackageName.PackageName+ deriving (Eq, Show)++fromCabal :: PackageName.PackageName -> PackageName+fromCabal = PackageName++toCabal :: PackageName -> PackageName.PackageName+toCabal (PackageName x) = x++fromString :: (Exception.MonadThrow m) => String -> m PackageName+fromString x =+ maybe (Exception.throwM $ InvalidPackageName.new x) (pure . fromCabal) $+ Parsec.simpleParsec x++toStringLiteral :: PackageName -> SourceText.StringLiteral+toStringLiteral x =+ SourceText.StringLiteral+ { SourceText.sl_st = SourceText.NoSourceText,+ SourceText.sl_fs = FastString.mkFastString . PackageName.unPackageName $ toCabal x,+ SourceText.sl_tc = Nothing+ }
source/test-suite/Main.hs view
@@ -98,6 +98,24 @@ "undefined = Example.undefined" "undefined = Example.undefined" + Hspec.it "adds a package qualified import" $ do+ expectImp+ ["--package=Data.SemVer:semver"]+ "version = Data.SemVer.initial"+ "import (implicit) qualified \"semver\" Data.SemVer\nversion = Data.SemVer.initial"++ Hspec.it "adds package qualified imports based on aliases" $ do+ expectImp+ ["--alias=Data.SemVer:V1", "--alias=Data.SemVer:V2", "--package=V1:semver", "--package=V2:semver-range"]+ "one = V1.initial\ntwo = V2.anyVersion"+ "import (implicit) qualified \"semver\" Data.SemVer as V1\nimport (implicit) qualified \"semver-range\" Data.SemVer as V2\none = V1.initial\ntwo = V2.anyVersion"++ Hspec.it "later packages override earlier ones" $ do+ expectImp+ ["--package=Data.SemVer:semver-range", "--package=Data.SemVer:semver"]+ "version = Data.SemVer.initial"+ "import (implicit) qualified \"semver\" Data.SemVer\nversion = Data.SemVer.initial"+ expectImp :: (Stack.HasCallStack) => [String] -> String -> String -> Hspec.Expectation expectImp arguments input expected = do before <- parseModule input