Enable All The Warnings

Max Tagher
Aug 9, 2018 · 11 min read

Summary: -Wall is not enough. -Weverything is not enough. Scroll down to “The Copy-Pastable List” for the list of full list of warnings you should use. Also: we’re hiring!

If you’re a Haskell user, you’re probably interested in having the compiler assist you in writing the most correct program possible. The compiler by default enables 18 warnings, and you can enroll in an additional 8 with -Wextra†, and even more with -Wall. You’ve probably seen packages compiling with -Wall, but did you know that -Wall doesn’t enable all of GHC’s warnings?

The GHC user’s guide lists what it doesn’t enable:

-Wincomplete-uni-patterns-Wincomplete-record-updates-Wmonomorphism-restriction-Wimplicit-prelude-Wmissing-local-signatures-Wmissing-exported-signatures-Wmissing-export-lists-Wmissing-import-lists-Wmissing-home-modules-Widentities-Wredundant-constraints-Wpartial-fields

I’ve also experimented and found that it also does not enable:

-Wmissed-specialisations-Wall-missed-specialisations-Wunsafe-Wsafe-Wtrustworthy-safe-Wcpp-undef

which aren’t listed in the docs‡; I opened a PR to fix this. The “safe”-related warnings are documented in the Safe Haskell section of the GHC user’s guide.

To truly get every warning, following the pattern of compilers like Clang, you need the -Weverything flag. If you turn it on, you’ll quickly find out that it gives you more warnings than you want, and you’ll want to turn some off. Here’s a breakdown — quoting liberally from the GHC user’s guide — of what each of the -Weverything flags does to help you decide which to keep:

Warnings you almost certainly want

-Wincomplete-uni-patterns

h = \[] -> 2
Just k = f y

I strongly encourage you to enable this warning, and to avoid writing incomplete pattern matches in general.

I do, however, recommend disabling this warning in tests, where it’s extremely useful for asserting that a function that returns Maybe or Either hits the branch you want, while also making its internal value usable. For example:

(Just updatedMetadata) <- runDB $ get metadataId
-- Do something with the metadata

It’s also useful if you make heavy use of the Smart Constructor pattern, and don’t want to write QuasiQuoters that allow you to safely construct them at compile-time.

Again, these exceptions are only meant for test environments, where failing on a random line is desirable. In real code, prefer handling errors (e.g. pattern matching both branches of Maybe), or preventing them from happening (e.g. using a QuasiQuoter to construct smart-constructed data).

-Wincomplete-record-updates

data Foo = Foo { x :: Int }
| Bar
f :: Foo -> Foo
f foo = foo { x = 6 }

I strongly encourage enabling this warning. Also: Because of this error, you should avoid mixing records with algebraic data types like this, which brings us to…

-Wpartial-fields

-Wmissing-home-modules

I recommend enabling it to prevent this problem. You should maybe also promote it to -Werror=missing-home-modules, to avoid confusing compiler errors.

Personally, I find the exposed-modules and other-modules lists tedious, frustrating, and error-prone. I recommend using hpack to automatically create your Cabal file to avoid this kind of error.

GHC 8.2+

-Widentities

-Wredundant-constraints

f :: (Eq a, Ord a) => a -> Bool -- Ord implies Eq, so Eq is unnecessary

and when a constraint is unused by the function body:

f :: (Eq a, Ord a) => a -> a -> Bool
f first second = first == second -- The function only uses ==, not functions like > or <, so Ord is unnecessary

-Wcpp-undef

#if FOO

It’s rare that Haskell code uses #if (#ifdef is more common in my experience), but you might as well turn it on.

-Wmissing-export-lists

1. You need an export list to not export “private” functions
2. It allows dead (unreachable) code to be detected
3. It “can ease optimizations like inlining”

Thus I would enable this warning. Note that it’s GHC 8.4.1+.

Warnings you *might* want

-Wmonomorphism-restriction

:set -XMonomorphismRestriction
set -Wmonomorphism-restriction
f xs = (len, len) where len = Data.List.genericLength xs

will trigger this warning:

<interactive>:14:25: warning: [-Wmonomorphism-restriction]
• The Monomorphism Restriction applies to the binding for ‘len’
Consider giving a type signature for ‘len’

This is because while genericLength can return any Num, without a type signature, GHC will not allow the two types in (len, len) to be different. It does this because if they were different types, it would have to compute len twice, which might be unexpected. Only if you give an explicit signature of (Num a, Num b) will GHC allow the types to be different.

I don’t have very good advice to give on this warning. Many people, like the author of hlint, Neil Mitchell, would prefer to see the restriction removed. It’s even known as “The Dreaded Monomorphism Restriction”.

So potentially you should disable the restriction with NoMonomorphismRestriction. If you keep it enabled, the monomorphism restriction won’t lead to bugs in your program, but it might lead to confusing behavior. This comment on Reddit gives an example:

a = 34b :: Int
b = a+5 -- Causes a to be inferred as Int
c = a/7 -- Refuses to compile

In practice, I turned on the monomorphism restriction warnings in our codebase, and found three negligible issues. It was more confusing trying to figure out why I’d be warned about them, when I could tell the code was perfectly sensible. For this reason, I keep the warnings off.

See this guide to GHC extensions for more details.

-Wimplicit-prelude

-Wmissing-local-signatures

ghci> let double a = a + a
*App|
<interactive>:6:5: warning: [-Wmissing-local-signatures]
Polymorphic local binding with no type signature:
double :: forall a. Num a => a -> a

I’m not sure why someone would be concerned about this. I turn this warning off.

-Wmissed-specialisations

Emits a warning if GHC cannot specialise an overloaded function, usually because the function needs an INLINABLE pragma. Reports when the situation arises during specialisation of an imported function.

This form is intended to catch cases where an imported function that is marked as INLINABLE (presumably to enable specialisation) cannot be specialised as it calls other functions that are themselves not specialised.

(See the next warning for commentary)

-Wall-missed-specialisations

I don’t recommend either of these warnings for normal use, because fixing them involves fixing underlying library code. Perhaps if you’re developing a performance critical library they’re a good choice to keep enabled, to prevent performance regressions. Alternatively, you could temporarily enable them when looking to optimize performance.

Warning: While I know that inlining is very important to Haskell performance, I don’t have much experience with it. If someone could comment on their experience using this warning to improve performance, that would be helpful.

Warnings you very likely don’t want

-Wmissing-import-lists

-Wunsafe

I do not use Safe Haskell, I’ve never heard of anyone using it (though I’m sure they’re out there), and many core libraries (Text, ByteString) are not Safe. I recommend disabling this warning, unless you’re intending to use Safe Haskell.

-Wsafe

Again, I would disable this warning.

-Wtrustworthy-safe

If you don’t use Safe Haskell, there’s no need to disable this warning, since you won’t be compiling modules with -XTrustworthy.

-Wmissing-exported-signatures

In other words, it disables the warnings you normally get when top-level signatures don’t have a declared type. Top-level signatures are strongly recommended for readability, to constrain your function’s abilities, and as a double check that the function you wrote is what you intended.

I personally consider it a bug that -Weverything enables this warning, since it makes -Weverything less restrictive than -Wall. I filed a Trac ticket here: https://ghc.haskell.org/trac/ghc/ticket/14794#ticket

The Copy-Pastable List

ghc-options:# For details on warnings: https://downloads.haskell.org/~ghc/master/users-guide/using-warnings.html# This list taken from https://medium.com/mercury-bank/enable-all-the-warnings-a0517bc081c3# Enable all warnings with -Weverything, then disable the ones we don’t care about- -Weverything- -Wno-missing-exported-signatures # missing-exported-signatures turns off the more strict -Wmissing-signatures. See https://ghc.haskell.org/trac/ghc/ticket/14794#ticket- -Wno-missing-import-lists # Requires explicit imports of _every_ function (e.g. ‘$’); too strict- -Wno-missed-specialisations # When GHC can’t specialize a polymorphic function. No big deal and requires fixing underlying libraries to solve.- -Wno-all-missed-specialisations # See missed-specialisations- -Wno-unsafe # Don’t use Safe Haskell warnings- -Wno-safe # Don’t use Safe Haskell warnings- -Wno-missing-local-signatures # Warning for polymorphic local bindings; nothing wrong with those.- -Wno-monomorphism-restriction # Don’t warn if the monomorphism restriction is used

-Wall plus warnings vs -Weverything minus warnings

  1. As you upgrade the compiler over the lifetime of your project, you’ll automatically enroll into new warnings. If you were excited to hear about -Wmissing-export-lists, -Wpartial-fields, or -Wmissing-home-modules(all relatively new warnings) that's good evidence that you'd benefit from an opt-out approach.
  2. You don’t need to know the full extent of -Wall's capabilities. If you looked at GHC's user guide to determine what warnings aren't enabled by -Wall, then picked from those, you'd be missing out on unlisted ones like -Wcpp-undef.

Counterargument: Some potential reasons to prefer -Wall plus warnings:

  1. You want to keep it simple. It’s more complicated than -Wall, plus a handful of warnings. This makes it worse if you want a simple list, which I could understand guides like RIO's wanting. It's much easier to explain why a warning is helpful than to explain e.g. why you wouldn't want something called Safe Haskell.
  2. You think users might use a new compiler with your code, and you don’t want them to get scary warnings. For example, if you’re writing a book, then you could plausibly expect readers to use a newer compiler than what you used when you wrote the book. If those users were using -Weverything, they might be confused by that your book's sample code generates warnings.

With the caveat that I had to create these objections myself, making them natural strawmen, I think these objections are fairly niche and most people should use -Weverything.

Disabling Warnings on a per-file basis

// Example of disabling warnings in a block of code using Clang#pragma clang diagnostic push#pragma clang diagnostic ignored “-Wshadow-ivar”// your code#pragma clang diagnostic pop

You can disable warnings on a per-file basis, though, by adding an OPTIONS pragma to the top of your file:

{-# OPTIONS -Wno-warn-unused-binds #-}module Foo where
...

Or on GHC versions prior to 8.0, as well as current GHC for backwards compatibility:

{-# OPTIONS -fno-warn-unused-binds #-}module Foo where

On -Werror

I’m less certain about developing a library, but I believe the best approach is to enable -Werroronly in CI. This is because users of your library (either humans or CI systems like Stackage) will eventually be trying your library on new versions of GHC. Either because you're using -Weverything or -Wcompat, or just because you've enabled -Wunused-imports, new GHC versions are very likely to trigger warnings that can be safely ignored. When those occur, it's frustrating to the end-users to run into these spurious build failures.

Bonus Warnings!

Initially on reading the GHC user guide, which describes -dcore-lint as being for the "really paranoid", I figured these almost never triggered, that they were warnings only for the Alastor Moody's of the world. So I ran them on our codebase, and got three findings before compilation stopped (output).

Assuming that these lints aren’t finding false positives, would it be helpful for more people to run them? I had never heard of them before, but something like Stackage could build all packages with those flags to look for GHC bugs.

† These numbers are from the counting the warnings at the top of the GHC user’s guide. But I think there’s a good chance those lists aren’t 100% accurate.

‡ That several warnings are missing from the GHC user guide’s list makes me suspicious that other warnings might not be, as well, but the only way to know for sure is to trigger them with -Weverythingand not with -Wall(and if you do find any, please do submit a PR to the GHC Github Mirror, on the warnings page—it's very easy and can be done from Github instead of Phabricator).

Interested in Haskell, Nix, or Typescript? Mercury is hiring an engineer

Mercury Engineering

Mercury's Engineering Blog

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store