Commit 20787529 authored by Edward Z. Yang's avatar Edward Z. Yang
Browse files

Thinning and renaming modules from packages on the command line.

This patch set adds support for extra syntax on -package and related
arguments which allow you to thin and rename modules from a package.
For example, this argument:

    -package "base (Data.Bool as Bam, Data.List)"

adds two more modules into scope, Bam and Data.List, without adding
any of base's other modules to scope.

These flags are additive: so, for example, saying:

    -hide-all-packages -package base -package "base (Data.Bool as Bam)"

will provide both the normal bindings for modules in base, as well as
the module Bam.

There is also a new debug flag -ddump-mod-map which prints the state
of the module mapping database.  H = hidden, E = exposed (so for
example EH says the module in question is exported, but in a hidden

Module suggestions have been minorly overhauled to work better with reexports:
if you have -package "base (Data.Bool as Bam)" and mispell Bam, GHC
will suggest "Did you mean Bam (defined via package flags to be
base:Data.Bool)"; and generally you will get more accurate information.
Also, fix a bug where we suggest the -package flag when we really need
the -package-key flag.

NB: The renaming afforded here does *not* affect what wired in
symbols GHC generates.  (But it does affect implicit prelude!)

ToDo: add 'hiding' functionality, to make it easier to support the alternative
prelude use-case.

ToDo: Cabal support

Signed-off-by: default avatarEdward Z. Yang <>

Test Plan: new tests and validate

Reviewers: simonpj, simonmar, hvr, austin

Subscribers: simonmar, relrod, ezyang, carter

Differential Revision:

GHC Trac Issues: #9375
parent 4accf601
......@@ -43,7 +43,7 @@ module DynFlags (
GhcMode(..), isOneShot,
GhcLink(..), isNoLink,
PackageFlag(..), PackageArg(..),
PackageFlag(..), PackageArg(..), ModRenaming,
Option(..), showOpt,
......@@ -190,6 +190,8 @@ import Data.Word
import System.FilePath
import System.IO
import System.IO.Error
import Text.ParserCombinators.ReadP hiding (char)
import Text.ParserCombinators.ReadP as R
import Data.IntSet (IntSet)
import qualified Data.IntSet as IntSet
......@@ -269,6 +271,7 @@ data DumpFlag
| Opt_D_dump_hi
| Opt_D_dump_hi_diffs
| Opt_D_dump_mod_cycles
| Opt_D_dump_mod_map
| Opt_D_dump_view_pattern_commoning
| Opt_D_verbose_core2core
......@@ -1025,8 +1028,10 @@ data PackageArg = PackageArg String
| PackageKeyArg String
deriving (Eq, Show)
type ModRenaming = Maybe [(String, String)]
data PackageFlag
= ExposePackage PackageArg
= ExposePackage PackageArg ModRenaming
| HidePackage String
| IgnorePackage String
| TrustPackage String
......@@ -1633,6 +1638,7 @@ dopt f dflags = (fromEnum f `IntSet.member` dumpFlags dflags)
enableIfVerbose Opt_D_dump_ticked = False
enableIfVerbose Opt_D_dump_view_pattern_commoning = False
enableIfVerbose Opt_D_dump_mod_cycles = False
enableIfVerbose Opt_D_dump_mod_map = False
enableIfVerbose _ = True
-- | Set a 'DumpFlag'
......@@ -2377,6 +2383,7 @@ dynamic_flags = [
, Flag "ddump-hpc" (setDumpFlag Opt_D_dump_ticked) -- back compat
, Flag "ddump-ticked" (setDumpFlag Opt_D_dump_ticked)
, Flag "ddump-mod-cycles" (setDumpFlag Opt_D_dump_mod_cycles)
, Flag "ddump-mod-map" (setDumpFlag Opt_D_dump_mod_map)
, Flag "ddump-view-pattern-commoning" (setDumpFlag Opt_D_dump_view_pattern_commoning)
, Flag "ddump-to-file" (NoArg (setGeneralFlag Opt_DumpToFile))
, Flag "ddump-hi-diffs" (setDumpFlag Opt_D_dump_hi_diffs)
......@@ -3349,7 +3356,26 @@ clearPkgConf = upd $ \s -> s { extraPkgConfs = const [] }
parsePackageFlag :: (String -> PackageArg) -- type of argument
-> String -- string to parse
-> PackageFlag
parsePackageFlag constr str = ExposePackage (constr str)
parsePackageFlag constr str = case filter ((=="").snd) (readP_to_S parse str) of
[(r, "")] -> r
_ -> throwGhcException $ CmdLineError ("Can't parse package flag: " ++ str)
where parse = do
pkg <- munch1 (\c -> isAlphaNum c || c `elem` ":-_.")
(do _ <- tok $ R.char '('
rns <- tok $ sepBy parseItem (tok $ R.char ',')
_ <- tok $ R.char ')'
return (ExposePackage (constr pkg) (Just rns))
return (ExposePackage (constr pkg) Nothing))
parseMod = munch1 (\c -> isAlphaNum c || c `elem` ".")
parseItem = do
orig <- tok $ parseMod
(do _ <- tok $ string "as"
new <- tok $ parseMod
return (orig, new)
return (orig, orig))
tok m = skipSpaces >> m
exposePackage, exposePackageId, exposePackageKey, hidePackage, ignorePackage,
trustPackage, distrustPackage :: String -> DynP ()
......@@ -47,8 +47,8 @@ import Data.IORef ( IORef, writeIORef, readIORef, atomicModifyIORef )
import System.Directory
import System.FilePath
import Control.Monad
import Data.List ( partition )
import Data.Time
import Data.List ( foldl' )
type FileExt = String -- Filename extension
......@@ -193,17 +193,17 @@ findExposedPackageModule hsc_env mod_name mb_pkg
LookupFound m pkg_conf ->
findPackageModule_ hsc_env m pkg_conf
LookupMultiple rs ->
return (FoundMultiple (map (packageConfigId . snd) rs))
return (FoundMultiple rs)
LookupHidden pkg_hiddens mod_hiddens ->
return (NotFound { fr_paths = [], fr_pkg = Nothing
, fr_pkgs_hidden = map (packageConfigId.snd) pkg_hiddens
, fr_mods_hidden = map (packageConfigId.snd) mod_hiddens
, fr_suggestions = [] })
return (NotFound{ fr_paths = [], fr_pkg = Nothing
, fr_pkgs_hidden = map (modulePackageKey.fst) pkg_hiddens
, fr_mods_hidden = map (modulePackageKey.fst) mod_hiddens
, fr_suggestions = [] })
LookupNotFound suggest ->
return (NotFound { fr_paths = [], fr_pkg = Nothing
, fr_pkgs_hidden = []
, fr_mods_hidden = []
, fr_suggestions = suggest })
return (NotFound{ fr_paths = [], fr_pkg = Nothing
, fr_pkgs_hidden = []
, fr_mods_hidden = []
, fr_suggestions = suggest })
modLocationCache :: HscEnv -> Module -> IO FindResult -> IO FindResult
modLocationCache hsc_env mod do_this = do
......@@ -280,8 +280,16 @@ findPackageModule hsc_env mod = do
Nothing -> return (NoPackage pkg_id)
Just pkg_conf -> findPackageModule_ hsc_env mod pkg_conf
-- | Look up the interface file associated with module @mod@. This function
-- requires a few invariants to be upheld: (1) the 'Module' in question must
-- be the module identifier of the *original* implementation of a module,
-- not a reexport (this invariant is upheld by @Packages.lhs@) and (2)
-- the 'PackageConfig' must be consistent with the package key in the 'Module'.
-- The redundancy is to avoid an extra lookup in the package state
-- for the appropriate config.
findPackageModule_ :: HscEnv -> Module -> PackageConfig -> IO FindResult
findPackageModule_ hsc_env mod pkg_conf =
ASSERT( modulePackageKey mod == packageConfigId pkg_conf )
modLocationCache hsc_env mod $
-- special case for GHC.Prim; we won't find it in the filesystem.
......@@ -526,11 +534,34 @@ cannotFindInterface = cantFindErr (sLit "Failed to load interface for")
cantFindErr :: LitString -> LitString -> DynFlags -> ModuleName -> FindResult
-> SDoc
cantFindErr _ multiple_found _ mod_name (FoundMultiple pkgs)
cantFindErr _ multiple_found _ mod_name (FoundMultiple mods)
| Just pkgs <- unambiguousPackages
= hang (ptext multiple_found <+> quotes (ppr mod_name) <> colon) 2 (
sep [ptext (sLit "it was found in multiple packages:"),
hsep (map ppr pkgs)]
hsep (map ppr pkgs) ]
| otherwise
= hang (ptext multiple_found <+> quotes (ppr mod_name) <> colon) 2 (
vcat (map pprMod mods)
unambiguousPackages = foldl' unambiguousPackage (Just []) mods
unambiguousPackage (Just xs) (m, ModOrigin (Just _) _ _ _)
= Just (modulePackageKey m : xs)
unambiguousPackage _ _ = Nothing
pprMod (m, o) = ptext (sLit "it is bound as") <+> ppr m <+>
ptext (sLit "by") <+> pprOrigin m o
pprOrigin _ ModHidden = panic "cantFindErr: bound by mod hidden"
pprOrigin m (ModOrigin e res _ f) = sep $ punctuate comma (
if e == Just True
then [ptext (sLit "package") <+> ppr (modulePackageKey m)]
else [] ++
map ((ptext (sLit "a reexport in package") <+>)
.ppr.packageConfigId) res ++
if f then [ptext (sLit "a package flag")] else []
cantFindErr cannot_find _ dflags mod_name find_result
= ptext cannot_find <+> quotes (ppr mod_name)
$$ more_info
......@@ -601,22 +632,40 @@ cantFindErr cannot_find _ dflags mod_name find_result
mod_hidden pkg =
ptext (sLit "it is a hidden module in the package") <+> quotes (ppr pkg)
pp_suggestions :: [Module] -> SDoc
pp_suggestions :: [ModuleSuggestion] -> SDoc
pp_suggestions sugs
| null sugs = empty
| otherwise = hang (ptext (sLit "Perhaps you meant"))
2 (vcat [ vcat (map pp_exp exposed_sugs)
, vcat (map pp_hid hidden_sugs) ])
(exposed_sugs, hidden_sugs) = partition from_exposed_pkg sugs
from_exposed_pkg m = case lookupPackage dflags (modulePackageKey m) of
Just pkg_config -> exposed pkg_config
Nothing -> WARN( True, ppr m ) -- Should not happen
pp_exp mod = ppr (moduleName mod)
<+> parens (ptext (sLit "from") <+> ppr (modulePackageKey mod))
pp_hid mod = ppr (moduleName mod)
<+> parens (ptext (sLit "needs flag -package") <+> ppr (modulePackageKey mod))
2 (vcat (map pp_sugg sugs))
-- NB: Prefer the *original* location, and then reexports, and then
-- package flags when making suggestions. ToDo: if the original package
-- also has a reexport, prefer that one
pp_sugg (SuggestVisible m mod o) = ppr m <+> provenance o
where provenance ModHidden = empty
provenance (ModOrigin{ fromOrigPackage = e,
fromExposedReexport = res,
fromPackageFlag = f })
| Just True <- e
= parens (ptext (sLit "from") <+> ppr (modulePackageKey mod))
| f && moduleName mod == m
= parens (ptext (sLit "from") <+> ppr (modulePackageKey mod))
| (pkg:_) <- res
= parens (ptext (sLit "from") <+> ppr (packageConfigId pkg)
<> comma <+> ptext (sLit "reexporting") <+> ppr mod)
| f
= parens (ptext (sLit "defined via package flags to be")
<+> ppr mod)
| otherwise = empty
pp_sugg (SuggestHidden m mod o) = ppr m <+> provenance o
where provenance ModHidden = empty
provenance (ModOrigin{ fromOrigPackage = e,
fromHiddenReexport = rhs })
| Just False <- e
= parens (ptext (sLit "needs flag -package-key")
<+> ppr (modulePackageKey mod))
| (pkg:_) <- rhs
= parens (ptext (sLit "needs flag -package-key")
<+> ppr (packageConfigId pkg))
| otherwise = empty
......@@ -891,13 +891,6 @@ hscGetSafe hsc_env m l = runHsc hsc_env $ do
| otherwise = pkgs
return (good, pkgs')
-- | A function which only qualifies package names if necessary; but
-- qualifies all other identifiers.
pkgQual :: DynFlags -> PrintUnqualified
pkgQual dflags = alwaysQualify {
queryQualifyPackage = mkQualPackage dflags
-- | Is a module trusted? If not, throw or log errors depending on the type.
-- Return (regardless of trusted or not) if the trust type requires the modules
-- own package be trusted and a list of other packages required to be trusted
......@@ -54,7 +54,7 @@ module HscTypes (
setInteractivePrintName, icInteractiveModule,
InteractiveImport(..), setInteractivePackage,
mkPrintUnqualified, pprModulePrefix,
mkQualPackage, mkQualModule,
mkQualPackage, mkQualModule, pkgQual,
-- * Interfaces
ModIface(..), mkIfaceWarnCache, mkIfaceHashCache, mkIfaceFixCache,
......@@ -637,7 +637,7 @@ data FindResult
-- ^ The module was found
| NoPackage PackageKey
-- ^ The requested package was not found
| FoundMultiple [PackageKey]
| FoundMultiple [(Module, ModuleOrigin)]
-- ^ _Error_: both in multiple packages
-- | Not found
......@@ -654,7 +654,7 @@ data FindResult
, fr_pkgs_hidden :: [PackageKey] -- Module is in these packages,
-- but the *package* is hidden
, fr_suggestions :: [Module] -- Possible mis-spelled modules
, fr_suggestions :: [ModuleSuggestion] -- Possible mis-spelled modules
-- | Cache that remembers where we found a particular module. Contains both
......@@ -1499,6 +1499,13 @@ mkQualPackage dflags pkg_key
(lookupPackage dflags pkg_key)
pkgid = sourcePackageId pkg
-- | A function which only qualifies package names if necessary; but
-- qualifies all other identifiers.
pkgQual :: DynFlags -> PrintUnqualified
pkgQual dflags = alwaysQualify {
queryQualifyPackage = mkQualPackage dflags
This diff is collapsed.
......@@ -2461,7 +2461,7 @@ warns if you hide something that the imported module does not export.
<title>Package-qualified imports</title>
<title id="package-qualified-imports">Package-qualified imports</title>
<para>With the <option>-XPackageImports</option> flag, GHC allows
import declarations to be qualified by the package name that the
......@@ -2484,7 +2484,9 @@ import "network" Network.Socket
added mainly so that we can build backwards-compatible versions of
packages when APIs change. It can lead to fragile dependencies in
the common case: modules occasionally move from one package to
another, rendering any package-qualified imports broken.</para>
another, rendering any package-qualified imports broken.
See also <xref linkend="package-thinning-and-renaming" /> for
an alternative way of disambiguating between module names.</para>
<sect3 id="safe-imports-ext">
......@@ -88,7 +88,11 @@ $ ghc-pkg list
to expose a hidden package or hide an exposed one. Only modules
from exposed packages may be imported by your Haskell code; if
you try to import a module from a hidden package, GHC will emit
an error message.
an error message. If there are a multiple exposed versions of a package,
GHC will prefer the latest one. Additionally, some packages may be
broken: that is, they are missing from the package database, or one of
their dependencies are broken; in this case; these packages are excluded
from the default set of packages.
......@@ -137,8 +141,11 @@ exposed-modules: Network.BSD,
(e.g. <literal>network-1.0</literal>) or the version
number can be omitted if there is only one version of the
package installed. If there are multiple versions
of <replaceable>P</replaceable> installed, then all other
versions will become hidden.</para>
of <replaceable>P</replaceable> installed and
<option>-hide-all-packages</option> was not specified, then all
other versions will become hidden. <option>-package</option>
supports thinning and renaming described in <xref
linkend="package-thinning-and-renaming" />.</para>
<para>The <option>-package <replaceable>P</replaceable></option>
option also causes package <replaceable>P</replaceable> to
......@@ -187,6 +194,8 @@ exposed-modules: Network.BSD,
more robust way to name packages, and can be used to
select packages that would otherwise be shadowed. Cabal
passes <option>-package-id</option> flags to GHC.
<option>-package-id</option> supports thinning and renaming
described in <xref linkend="package-thinning-and-renaming" />.
......@@ -363,6 +372,52 @@ _ZCMain_main_closure
<sect2 id="package-thinning-and-renaming">
<title>Thinning and renaming modules</title>
<para>When incorporating packages from multiple sources, you may end up
in a situation where multiple packages publish modules with the same name.
Previously, the only way to distinguish between these modules was to
use <xref linkend="package-qualified-imports" />. However, since GHC 7.10,
the <option>-package</option> flags (and their variants) have been extended
to allow a user to explicitly control what modules a package brings into
scope, by analogy to the import lists that users can attach to module imports.
The basic syntax is that instead of specifying a package name P to the package
flag <literal>-package</literal>, instead we specify both a package name and a
parenthesized, comma-separated list of module names to import. For example,
<literal>-package "base (Data.List, Data.Bool)"</literal> makes only
<literal>Data.List</literal> and <literal>Data.Bool</literal> visible from
package <literal>base</literal>.
We also support renaming of modules, in case you need to refer to both modules
simultaneously; this is supporting by writing <literal>OldModName as
NewModName</literal>, e.g. <literal>-package "base (Data.Bool as
Bool)</literal>. It's important to specify quotes
so that your shell passes the package name and thinning/renaming list as a
single argument to GHC.</para>
<para>Package imports with thinning/renaming do not hide other versions of the
package: e.g. if containers-0.9 is already exposed, <literal>-package
"containers-0.8 (Data.List as ListV8)"</literal> will only add an additional
binding to the environment. Similarly, <literal>-package "base (Data.Bool as
Bool)" -package "base (Data.List as List)"</literal> is equivalent to
<literal>-package "base (Data.Bool as Bool, Data.List as List)"</literal>.
Literal names must refer to modules defined by the original package, so for
example <literal>-package "base (Data.Bool as Bool, Bool as Baz)"</literal> is
invalid unless there was a <literal>Bool</literal> module defined in the
original package. Hiding a package also clears all of its renamings. </para>
You can use renaming to provide an alternate prelude, e.g.
<literal>-hide-all-packages -package "basic-prelude (BasicPrelude as
Prelude)"</literal>, in lieu of the <xref
linkend="rebindable-syntax">NoImplicitPrelude</xref> extension.
<sect2 id="package-databases">
<title>Package Databases</title>
......@@ -39,7 +39,7 @@ import HscTypes ( tyThingParent_maybe, handleFlagWarnings, getSafeMode, hsc_IC,
setInteractivePrintName )
import Module
import Name
import Packages ( trusted, getPackageDetails, listVisibleModuleNames )
import Packages ( trusted, getPackageDetails, listVisibleModuleNames, pprFlag )
import PprTyThing
import RdrName ( getGRE_NameQualifier_maybes )
import SrcLoc
......@@ -2333,15 +2333,7 @@ showPackages = do
let pkg_flags = packageFlags dflags
liftIO $ putStrLn $ showSDoc dflags $ vcat $
text ("active package flags:"++if null pkg_flags then " none" else "")
: map showFlag pkg_flags
where showFlag (ExposePackage a) = text $ showArg a
showFlag (HidePackage p) = text $ " -hide-package " ++ p
showFlag (IgnorePackage p) = text $ " -ignore-package " ++ p
showFlag (TrustPackage p) = text $ " -trust " ++ p
showFlag (DistrustPackage p) = text $ " -distrust " ++ p
showArg (PackageArg p) = " -package " ++ p
showArg (PackageIdArg p) = " -package-id " ++ p
showArg (PackageKeyArg p) = " -package-key " ++ p
: map pprFlag pkg_flags
showPaths :: GHCi ()
showPaths = do
......@@ -33,7 +33,7 @@ import InteractiveUI ( interactiveUI, ghciWelcomeMsg, defaultGhciSettings )
import Config
import Constants
import HscTypes
import Packages ( dumpPackages, simpleDumpPackages )
import Packages ( dumpPackages, simpleDumpPackages, pprModuleMap )
import DriverPhases
import BasicTypes ( failed )
import StaticFlags
......@@ -217,6 +217,11 @@ main' postLoadMode dflags0 args flagWarnings = do
when (verbosity dflags6 >= 3) $ do
liftIO $ hPutStrLn stderr ("Hsc static flags: " ++ unwords staticFlags)
when (dopt Opt_D_dump_mod_map dflags6) . liftIO $
printInfoForUser (dflags6 { pprCols = 200 })
(pkgQual dflags6) (pprModuleMap dflags6)
---------------- Final sanity checking -----------
liftIO $ checkOptions postLoadMode dflags6 srcs objs
<no location info>:
Could not find module ‘Control.Monad.Trans.State’
It is not a module in the current program, or in any known package.
Perhaps you meant
Control.Monad.Trans.State (from transformers-
Control.Monad.Trans.Class (from transformers-
Control.Monad.Trans.Cont (from transformers-
include $(TOP)/mk/
include $(TOP)/mk/
hide_all = '-hide-all-packages -XNoImplicitPrelude '
incr_containers = '-package "containers (Data.Map as Map, Data.Set)" '
inc_containers = '-package containers '
incr_ghc = '-package "ghc (HsTypes as MyHsTypes, HsUtils)" '
inc_ghc = '-package ghc '
hide_ghc = '-hide-package ghc '
test('package01', normal, compile, [hide_all + incr_containers])
test('package01e', normal, compile_fail, [hide_all + incr_containers])
test('package02', normal, compile, [hide_all + inc_containers + incr_containers])
test('package03', normal, compile, [hide_all + incr_containers + inc_containers])
test('package04', normal, compile, [incr_containers])
test('package05', normal, compile, [incr_ghc + inc_ghc])
test('package06', normal, compile, [incr_ghc])
test('package06e', normal, compile_fail, [incr_ghc])
test('package07e', normal, compile_fail, [incr_ghc + inc_ghc + hide_ghc])
test('package08e', normal, compile_fail, [incr_ghc + hide_ghc])
test('package09e', normal, compile_fail, ['-package "containers (Data.Map as M, Data.Set as M)"'])
test('package10', normal, compile, ['-hide-all-packages -package "ghc (UniqFM as Prelude)" '])
module Package01 where
import Map
import Data.Set
module Package01e where
import Data.Map
import Data.IntMap
Failed to load interface for ‘Data.Map’
It is a member of the hidden package ‘containers-’.
Use -v to see a list of the files searched for.
Failed to load interface for ‘Data.IntMap’
It is a member of the hidden package ‘containers-’.
Use -v to see a list of the files searched for.
module Package02 where
import Data.Map
import Map
import Data.Set
import Data.IntMap
module Package03 where
import Data.Map
import Map
import Data.Set
import Data.IntMap
module Package04 where
import Data.Map
import Map
import Data.Set
import Data.IntMap
module Package05 where
import HsTypes
import MyHsTypes
import HsUtils
module Package06 where
import MyHsTypes
import HsUtils
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment