packages feed

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 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