If you’ve used DAML Studio or the command line compiler, you will have noticed that it warns you about potential issues in your code.
In the example above, I get a warning for a redundant import of Daml.Script
and I also get a warning about a pattern match that is not-exhaustive (I didn’t match on Right
).
These are not fatal errors. The compiler can still produce a DAR and you can test out how your UI works against this model. However, they are usually issues worth investigating before you merge your changes since they either indicate bad practices (redundant imports) or point to actual errors (non-exhaustive pattern matches).
Turning warnings into errors
While it is convenient to test out models that still have warnings, you probably don’t want to commit them. To enforce that you can make the compiler fail if there are any warnings. You can then use this in CI so any builds with warnings will fail and no warnings accidentally make it through to the code that you deploy to production. All you need to change here is to switch from daml build
to daml build --ghc-option=-Werror
and all warnings will instead result in fatal errors!
Enabling additional warnings
The default set of warnings enabled by the DAML compiler is fairly conservative. Especially, when you are just starting out seeing a huge set of warnings can easily be overwhelming and distract from what you are actually working on. However, as you get more experienced and more people work on the same projects enabling additional warnings (and enforcing their absence in CI) can be useful.
All of these warnings can be enabled by adding --ghc-option=-W<warningname>
to the list of build-options
in your daml.yaml
. You can disable warnings via --ghc-option=-Wno-<warningname>
.
unused-top-binds
This warning tells you if you have unused top-level definitions, i.e., dead code that is not used anywhere and can be deleted. Note that this only works if you specify the list of exported definitions explicitly. Otherwise all top-level definitions are exported implicitly so they could be used from another module and therefore cannot necessarily be deleted. To specify them explicitly switch from
module A where
to
module A (export1, export2) where
.
unused-matches
Note: This requires SDK 1.3.0-snapshot.20200623.4546.0.4f68cfc4 or newer to work properly. For older versions, templates produce false positives. Special thanks to @shaynefletcher for fixing this.
-Wunused-matches
tells you about patttern matches where you bound a variable but didn’t use it. This can indicate that you passed an unused parameter to a function, e.g.,
f x = 0
Here you will get a warning about x
being unused. If you do this intentionally you can replace the x
by an underscore or any variable name starting with an underscore, e.g.,
f _x = 0
unused-do-bind
This is similar to unused-matches
but specific to do
-blocks.
f p = do
cid <- submit p (createCmd …)
pure ()
Here you will get a warning that cid
is unused. As with unused-matches
you can silence it by prefixing the variable with an underscore.
incomplete-uni-patterns
By default, the compiler warns you about non-exhaustive pattern matches. But it doesn’t warn you in places where you can only specify one pattern, e.g., a lambda or a let
-binding. incomplete-uni-patterns
also warns about those cases.
f = (\(Left x) -> x)
redundant-constraints
As the name suggests, this warning warns you about redundant constraints on a definition, e.g.,
f : Eq a => a -> a
f x = x
There is no reason to add the Eq a
constraint here since we are not using ==
or /=
. The extra constraint makes our function less useful and also incurs a runtime overhead.
missing-signatures
While the DAML compiler is often able to infer the type of an expression, it is good practice to annotate top-level definitions with an explicit type signature to make your code easier to read, get better compile errors on type mismatches and potentially avoid runtime overhead from making your function more general than you intended to. -Wmissing-signatures
warns you about any top-level definition without a type signature. Note that you need to write the type signature separately to silence the warning.
f (x: Int) : Int = x + 1
will still produce the warning while
f : Int -> Int
f x = x + 1
will not.
Enabling all common warnings
Enabling all warnings individually can get tedious. Instead you can add --ghc-option=-Wall
to your daml.yaml
enable all of them and then selectively disable warnings via --ghc-option=-Wno-<warningname>
that you don’t want to use on your project
What are your favorite warnings?
Did i miss any that you consider helpful? Which one helps you the most in writing better code with less errors?