1 Fine-Grained Unused Warnings¶
This proposal aims to make warnings about unused names more precise.
1.1 Motivation¶
a :: Int
a = 4
where bar = foo
foo = 3
Compiling the above code with -Wall
results in the
these warnings:
Defined but not used: `bar'
Defined but not used: `foo'
Sometimes, I look at a warning like this and think “I could have sworn I used
foo
somewhere” - but of course, what the warning is trying to say isn’t
actually that foo
is unused, but rather that all of foo
’s usage sites
are in unused bindings.
Furthermore, while it’s useful to see such a warning for CI, to immediately know all of the bindings that have to be removed to eliminate the warnings, during development, these additional warnings take up a lot of valuable screen real estate in ghcid.
1.2 Proposed Change Specification¶
The proposed change aims to distinguish between genuinely (or directly) unused bindings and indirectly unused bindings. While the warnings for directly unused bindings remain unchanged, the warnings for indirectly unused bindings will now be controlled by a new flag, -Windirectly-unused-binds
, which is enabled by default.
Relevant warning flag. The relevant warning flag for a variable binding is defined as follows:
The relevant warning flag for top-level binds is
-Wunused-top-binds
The relevant warning flag for binds defined in
where
orlet
blocks is-Wunused-local-binds
The relevant warning flag for variables bound by a pattern match is
-Wunused-matches
The relevant warning flag for type variables bound by
forall
is-Wunused-foralls
The relevant warning flag for modules and names from modules that are being imported is
-Wunused-imports
The relevant warning flag for type variables bound in type patterns is
-Wunused-type-patterns
The proposal changes some of the warnings produced by each of these flags, except for
-Wunused-type-patterns
, which is listed here for completeness.Note: Related warning flags that are not affected by this proposal since they are not about binding or introducing names are
-Wunused-pattern-binds
-Wunused-packages
-Wunused-do-bind
Directly Unused Bindings: A binding B is directly unused if it is referenced only in B’s own strongly-connected component, and the relevant warning flag is enabled. A binding B includes value bindings, but also (at the top level) type and class declarations.
Viewing a set of definitions as a graph where each binding form a vertex, and each reference in the binding’s body to another binding forms a directed edge, the strongly connected component of a vertex B is the largest possible set of vertices including B such there is a path from any vertex to any other vertex.
Indirectly Unused Bindings: A binding B is indirectly unused if it is directly unused, or B is referenced only in the body of a (directly or indirectly) unused binding C, and C is in scope at the point where B’s definition appears, and the relevant warning flag is enabled.
For example, suppose
foo1
andfoo2
appear nowhere else.bar1 = True foo1 = bar1 foo2 = (bar2, foo1) where bar2 = True
In this example
foo2
is directly unusedfoo1
is indirectly unused, because it only occurs in the body of the unusedfoo2
, andfoo1
is in scope at the point offoo2
’s definition.Similarly,
bar1
is indirectly unused.But
bar2
is not indirectly unused, because, while it occurs in the body of the unusedfoo2
,bar2
is not in scope atfoo2
’s definition site.
A variable V bound by a pattern match, assuming
-Wunused-matches
is enabled,is directly unused if it does not appear in the alternative the pattern match belongs to
is indirectly unused if it referenced only in the body of a (directly or indirectly) unused bindings C, and V is in scope at the point where C’s definition appears.
For example, suppose
bar1
andbar2
appear nowhere else.bar1 (Just v1) = undefined bar2 (Just v2) v3 = v3 where c = v2
In this example
c
is directly unusedv1
is directly unusedv2
is indirectly unusedv3
is not indirectly unused, because, while it only occurs in the unusedbar2
,v3
is not in scope at the atbar2
’s definition site.
An imported identifier, assuming
-Wunused-imports
is enabled,is directly unused if it is not mentioned anywhere in the module
is indirectly unused if it is referenced only in (directly or indirectly) unused bindings
A forall-bound type variable, assuming
-Wunused-foralls
is enabled,is directly unused if it does not appear in the body of the type
is indirectly unused if it only appears in the kind signature of other (directly or indirectly) unused
forall
-bound type variables in the body of the type
For example:
far :: forall a (b :: a) c . c
Here,
b
is directly unused, buta
is indirectly unused.
Warning References and Messages:
A binding will produce a warning if
it is directly unused, or
it is indirectly unused and
-Windirectly-unused-binds
is enabled
The warnings for (directly or indirectly) unused bindings will reference all bindings they are used in. For example, if
-Wunused-top-binds
and-Wunused-local-binds
are enabled,foo = bar baz = pureStrLn "Hi" where quux = bar bar = ... main = baz
will produce three warnings:
foo
is directly unusedquux
is directly unusedbar
is indirectly unused, and will produce a warning statingwarning: [-Wunused-top-binds, -Windirectly-unused-binds] ‘bar' is defined but used only in the following unused bindings: ‘foo’, ‘quux’
If the warning for an unused binding B would reference multiple nested bindings it will only reference the innermost (directly or indirectly) unused binding(s) of those. For example, suppose
bar
ismodule M(f) where f = 22 foo = 7 wombat = 8 bar = quux + 2 where quux = foo * 2 wux = wombat + 1
In this example,
quux
is not unused (it is used in the right-hand side ofbar
, andquux
is not in scope atbar
’s definition site), whilewux
andbar
are directly unused.The binding
wombat
is indirectly unused; it’s warning will mentionwux
(the innermost unused binding in whichwombat
is mentioned).The binding for
foo
is also indirectly unused, but its warning will mentionbar
(notquux
) sincebar
is the innermost unused binding enclosing the refernce tofoo
.
1.3 Examples¶
1.3.1 General Example¶
module Foo () where
import Data.List as L
foo = L.intercalate bar
bar = baz
where baz = undefined
quux = wibble
wibble = worble
worble = quux
wirble = quux
far :: forall a (b :: a) c . c
far = far
bar1 (Just v1) = undefined
bar2 (Just v2) v3 = v3
where c = v2
Currently, without this proposal, the file results in the following warnings, assuming -Wunused-imports
, -Wunused-top-binds
, -Wunused-local-binds
, -Wunused-matches
, and -Wunused-foralls
are enabled:
Foo.hs:5:1: warning: [-Wunused-top-binds]
Defined but not used: ‘foo’
Foo.hs:7:1: warning: [GHC-38417] [-Wmissing-signatures]
Top-level binding with no type signature: bar :: a
Foo.hs:7:1: warning: [-Wunused-top-binds]
Defined but not used: ‘bar’
Foo.hs:9:9: warning: [-Wunused-local-binds]
Defined but not used: ‘quux’
Foo.hs:10:9: warning: [-Wunused-local-binds]
Defined but not used: ‘wibble’
Foo.hs:11:9: warning: [-Wunused-local-binds]
Defined but not used: ‘worble’
Foo.hs:12:9: warning: [-Wunused-local-binds]
Defined but not used: ‘wirble’
Foo.hs:14:17: warning: [-Wunused-foralls]
Unused quantified type variable ‘(b :: a)’
In the type signature for ‘far’
Foo.hs:15:1: warning: [-Wunused-top-binds]
Defined but not used: ‘far’
Foo.hs:19:1: warning: [-Wunused-top-binds]
Defined but not used: `bar1'
Foo.hs:19:12: warning: [-Wunused-matches]
Defined but not used: `v1'
Foo.hs:20:1: warning: [-Wunused-top-binds]
Defined but not used: `bar2'
Foo.hs:21:9: warning: [-Wunused-local-binds]
Defined but not used: `c'
With this proposal, these warnings would be produced instead, assuming -Windirectly-unused-binds
is enabled:
Foo.hs:3:1: warning: [-Wunused-imports, -Windirectly-unused-binds]:
The import of ‘Data.List’ is used only by the following unused binding: ‘foo’
except perhaps to import instances from ‘Data.List’
To import instances alone, use: import Data.List()
Foo.hs:5:1: warning: [-Wunused-top-binds]
Defined but not used: ‘foo’
Foo.hs:7:1: warning: [-Wunused-top-binds, -Windirectly-unused-binds]
‘bar' is defined but used only in the following unused binding: ‘foo’
Foo.hs:9:9: warning: [-Wunused-local-binds, -Windirectly-unused-binds]
‘quux' is defined but used only in the following unused bindings: ‘worble’, ‘wirble’
Foo.hs:10:9: warning: [-Wunused-local-binds, -Windirectly-unused-binds]
‘wibble' is defined but used only in the following unused binding: ‘worble’
Foo.hs:11:9: warning: [-Wunused-local-binds, -Windirectly-unused-binds]
‘worble' is defined but used only in the following unused binding: ‘wibble’
Foo.hs:12:9: warning: [-Wunused-local-binds]
Defined but not used: ‘wirble’
Foo.hs:13:15: warning: [-Wunused-foralls, -Windirectly-unused-binds]
Quantified type variable ‘a’ is used only in the following unused variable: ‘(b :: a)’
In the type signature for ‘far’
Foo.hs:13:17: warning: [-Wunused-foralls]
Unused quantified type variable ‘(b :: a)’
In the type signature for ‘far’
Foo.hs:14:1: warning: [-Wunused-top-binds]
Defined but not used: ‘far’
Foo.hs:19:1: warning: [-Wunused-top-binds]
Defined but not used: ‘bar1’
Foo.hs:19:12: warning: [-Wunused-matches]
Defined but not used: ‘v1’
Foo.hs:19:12: warning: [-Wunused-matches, -Windirectly-unused-binds]
‘v1’ is defined but used only in the following unused bindings: ‘c’
Foo.hs:20:1: warning: [-Wunused-top-binds]
Defined but not used: ‘bar2’
Foo.hs:21:9: warning: [-Wunused-local-binds]
Defined but not used: ‘c’
1.3.2 Recursive and Mutually Recursive Bindings¶
Take this as example:
b1 = b2
b2 = b3
b3 = b1
Currently, these are the warnings GHC produces:
UnusedRecursion.hs:7:1: warning: [-Wunused-top-binds]
Defined but not used: ‘b1’
UnusedRecursion.hs:9:1: warning: [-Wunused-top-binds]
Defined but not used: ‘b2’
UnusedRecursion.hs:11:1: warning: [-Wunused-top-binds]
Defined but not used: ‘b3’
With this proposal:
From point 1. we can infer that if a binding is used only (mututally) recursively, it is directly unused.
For mutually recursive bindings, if none of the bindings in the group are used outside their mutual recursion, each binding in the group is directly unused. The warning for each binding will list the other bindings in the group it is directly involved with, so we have
UnusedRecursion.hs:7:1: warning: [-Wunused-top-binds]
‘b1’ is defined but used only in the following unused bindings: ‘b2’, ‘b3’
UnusedRecursion.hs:9:1: warning: [-Wunused-top-binds]
‘b2’ is defined but used only in the following unused bindings: ‘b1’, ‘b3’
UnusedRecursion.hs:11:1: warning: [-Wunused-top-binds]
‘b3’ is defined but used only in the following unused bindings: ‘b1’, ‘b2’
1.3.3 Example illustrating relevant warnings flags¶
{-# OPTIONS_GHC -Wunused-local-binds -Windirectly-unused-binds #-}
foo = bar
where
bar = 4
Currently, this produces no warnings.
Looking at this, it might appear that bar
is indirectly unused, and will thus produce a warning under this proposal. However, this is not the case: foo
does not qualify as directly unused, since its relevant warning flag -Wunused-top-binds
is not active, and so bar
also does not qualify as indirectly unused. Thus, there will still not be any warnings produced by the code under this proposal.
1.4 Effect and Interactions¶
For the most part, the effects of this proposal are minor. The main differences for existing code-bases are that the warning message GHC prints for the indirect warnings is different, although due to the more consistent treatment of warning flags, existing code bases can also sometimes get more or fewer warnings in cases of indirectly unused bindings where two warning flags interact with one another. There can also be additional warnings about indirectly unused imports and forall binds.
Since the warnings don’t have any special formats, existing tools should be able to handle them without issues.
Users that don’t wish to see warnings about indirectly unused bindings can turn those warnings off.
1.5 Costs and Drawbacks¶
The warning mechanism is somewhat more complicated and as a consequence might have a somewhat higher maintenance cost, which might especially concern listing out all the other bindings that an indirectly unused binding is referenced by.
1.6 Alternatives¶
We could combine warnings of unused bindings and the indirect non-uses they induce. This could be similar to how error locations are combined in a single error message for duplicate declarations.
A possible advantage is that we could simplify the mechanism by removing the configurability of turning the warnings off. This would still give us the benefit of reducing the potential for confusion from these warnings, however users that wish to turn these warnings off could not do so.
A disadvantage is that most third-party tools dealing with error messages will likely have a harder time parsing the warning messages.
A different name could be chosen for the new flag,
-Windirectly-unused-binds
. For example: *-freport-indirect-uses
Instead of
-Windirectly-unused-binds
, we could separate each warning flag (like-Wunused-imports
) into two (like-Windirectly-unused-imports
and-Wdirectly-unused-imports
) and a warnings group like-Wno-indirect-uses
to turn off all warnings about indirectly unused bindings at once.This would offer more configurability if users want to see some warnings about indirectly unused bindings but not others.
It would require a higher number of warning flags.
1.7 Implementation Plan¶
@Jade has expressed interest in implementing this proposal. As a fallback, I would be able to find time to implement it.