Unused imports describes how the current unused imports warning works. An import is unused if it can be deleted without changing the meaning of the program; currently, GHC strives to report all such imports (although it doesn't guarantee that it will do so).
In this proposal, we identify some unused imports for which we should NOT report a warning. The underlying motivation is that, assuming a module M compiles without unused import warnings, if one of the modules it imports adds an extra export, then M should continue to compile without warnings. (This is equivalent to stating that a minor version upgrade under the Haskell PVP should not trigger new warnings.)
This is achieved is as follows: an import is unused if it can be deleted without changing the meaning of the program, EXCEPT if the import explicitly brings x into scope (e.g., import A (x)), and the only other ways x was brought into scope are implicit (e.g., import B), AND each such implicit import is of a different module (e.g., import A (x) is still redundant if we import A).
The reason the CPP is necessary is because in base-4.8, an export of pure was added to Prelude; this means that Control.Applicative's import of pure is redundant.
Although the import is technically redundant, it is extremely inconvenient, because a minor, backwards-compatible change to a package caused a redundant import warning to surface. In order for this code to be warning-free on the newer version of base, we must omit pure from Control.Applicative; however, the older version of base will not compile without this error! Instead, we would like import Control.Applicative ((<*), pure) to NOT report any warnings, even if Prelude starts exporting pure.
See also #10117 for a problem with a common workaround for this problem.
Say that an import-item is either an entire import-all decl (eg import Foo),
or a particular item in an import list (e.g., import Foo( ..., x, ...)). An
imported name can be provided implicitly or explicitly by an import item; specifically
an imported name x is explicitly provided if it appears textually in the original
import statement, and implicitly otherwise (i.e., if there was no import list, or
it was brought into scope via ellipses C(..)).
Then, for each use of an imported name, we will attribute
that use to all import-items which implicitly provide it, AND one import-item
which explicitly provides it. Then, any import items with no
uses attributed to them are unused, and are warned about. I've highlighted
in bold the differences from the previous specification.
For every RdrName in the program text, find all the import-items that brought it into scope. The lookup mechanism on RdrNames already takes account of whether the RdrName was qualified, and which imports have the right qualification etc, so this step is very easy.
Partition the import-items for a RdrName by their Module (e.g. A of import A) and whether or not the explicitly and implicitly provide the RdrName (import Prelude implicitly provides Just; import Prelude (Just) and import Prelude (Maybe(..)) explicitly provides Just.). For each Module, mark one implicit import-item (if it exists) as used. Among the explicit imports of a RdrName whose Modules had no implicit import-items associated with them, pick one and mark it as used.
Now bleat about any import-items that are unused. For a decl
import Foo(x,y), if both the x and y items are unused, it'd be better
to bleat about the entire decl rather than the individual items.
Step (2) is nondeterministic in two ways: the selection of the implicit import-item (per Module), and the selection of the explicit import item (across all Modules.) We need some total order over import-items to let us decide which to pick: we just prefer the textually first.
The addition is a bit involved, but essentially we want to compute some set of import-items to mark as used. Here are some important motivating examples:
For each module, mark one implicit import-item as used.
-- Prelude and Control.Applicative export pure-- We want no errors here; thus, we need to mark both Prelude-- and Control.Applicative as used. These are different modules,-- so we mark each of their implicit imports as used.import Preludeimport Control.Applicativebar = pure-- We want to report a warning here; for any module (in this case-- Control.Applicative) we only mark one import-item as used.import Control.Applicativeimport Control.Applicativebar = pure
Among the explicit imports whose Modules had no implicit import-items associated with them, pick one and mark it as used.
-- We want to report the second import as redundant. Thus, because-- M has an implicit "import M", we do not mark the second import as-- used.import Mimport M (x)-- We don't want to report any warning here. We mark Control.Applicative-- as used because there is no "import Control.Applicative"import Preludeimport Control.Applicative (pure)-- We want to report one import as redundant. Thus, among the eligible-- explicit import-items (across module names), we only mark one as used.import M (x)import N (x)
import Prelude (Maybe(..)) is an explicit import of Just.
-- Prelude and Data.Maybe export Maybe(Just, Nothing)-- We want to report a warning here; since Maybe(..) is an explicit-- import we only pick one import item to mark as used.import Prelude (Maybe(..))import Data.Maybe (Just)bar = Just
Consider these examples, where Foo exports x and y, and FooPlus re-exports all of Foo, plus z:
module X0 where module X1 where import Foo import Foo import Foo( x ) import Foo( x ) bar = x bar = x+y module X2 where module X3 where import Foo( x, y ) import Foo( x, y ) import Foo( x ) import Foo( x ) bar = x bar = x + y module X4 where module X5 where import Foo( x, y ) import Foo( x, y ) as Bar import Foo( x, y ) import Foo( x, y ) bar = x + y bar = x + Bar.y module X6 where module X7 where import Foo( x, y ) as Bar import FooPlus(x,y) import Foo( x, y ) import FooPlus(y,z) bar = Foo.x + Bar.y import FooPlus(z,x) bar = (x,y,z) module X8 import Control.Monad import Control.Monad.State import Control.Monad.Reader -- using 'when' (exported by Control.Monad and .State), -- 'State' and 'Reader'
Under this proposal, we get the following behavior:
X0: import Foo ( x ) redundant (there is an implicit import of Foo).
X1: import Foo ( x ) redundant (there is an implicit import of Foo).
X2: y of import Foo ( x, y ) redundant, and import Foo ( x ) redundant (in both cases, we only picked one explicit import to mark as used).
X3: import Foo ( x ) redundant (ditto).
X4: Second import Foo ( x, y ) redundant (ditto).
X5: import Foo ( x, y ) redundant (only first import's y can be marked as used by Bar.y name).
X6: x in import Foo( x, y ) as Bar and y in import Foo( x, y ) redundant as they were not marked used
X7: import FooPlus(z,x) and y in second import redundant
X8: NOTHING marked redundant! (Use of when marks both Control.Monad and Control.Monad.State as used)