Live Demo
Blog   >   DevSecOps   >   Useful Scala Compiler Options for Better Scala Development: Part 1

Useful Scala Compiler Options for Better Scala Development: Part 1

At Threat Stack, we like to leverage our tools to the fullest. Since we use Scala, it only makes sense for us to always be looking into ways of getting the most out of the Scala Compiler to enhance our productivity. And, as it turns out, the Scala Compiler offers a number of features that makes our lives way better! 

A couple of resources make great recommendations about what options you should pass to the Scala Compiler. (Rob Norris’ Blog comes to mind.) However, most of the resources don’t really explain why you want to enable these options. In this series, I’ll cover a number of Scala Compiler options that ease development. I’ll discuss ones that we use at Threat Stack as well as other common options that we have opted not to use. Where applicable, I’ll show examples of issues in code that will be caught by the compiler once the correct options have been enabled.

Note: At Threat Stack we use Scala 2.11.8 and Java 8, so all the examples and options in this post will focus on these versions of Scala and the JVM.

Note: Part 2 of this series is now available: Useful Scala Compiler Options, Part 2: Advanced Language Features

Generally Useful Flags

Here is an example of some basic flags that we use:

scalacOptions ++= Seq(
  "-encoding", "UTF-8",

Target JVM

We always define what our target JVM is for object files. At Threat Stack, we target JVM 1.8, so we use the -target:jvm-1.8. It’s important to note that while JVMs 1.5 through 1.8 are supported, JVM 1.5 is deprecated, and its use will generate a warning.

File Encoding

We like to set the encoding of all our files with the -encoding option. It is important to note that the -encoding option actually takes an argument (in our case “UTF-8”). Not all of our developers use the same operating systems, and sometimes these different operating systems have different default character encodings. In the rare event that a file gets saved with a different encoding, the compiler can catch this issue and warn us about it.

Unchecked Types

We use the -unchecked option to provide us with more detailed information about type erasure warnings. Even though Scala manages to deal with some of the pain from Java’s type system, at the end of the day you are still compiling to the JVM and have to be aware of type erasure. Type erasure sometimes rears its ugly head when parameterized types and pattern matching meet:

def printListType(lst: List[AnyVal]) = lst match {
 case strings: List[String] => println(s"List of Strings $strList")
 case integers: List[Int] => println(s"List of Strings $intList")

This will generate warnings saying that strings and integers cannot be type checked due to erasure, and as a result, the integers case is actually unreachable. Obviously, having every instance of lst be caught by the first case and the second case being unreachable is not desirable, so catching this error becomes quite helpful.

Deprecation Warnings

Sometimes, we accidentally make use of a deprecated class, method, or value. When this happens, the Scala compiler gives a relatively useless warning by default:

[warn] there were three deprecation warnings; re-run with -deprecation for details 

That doesn’t even give us a hint about where to look. However, with -deprecation, we see an informative warning:

[warn] Foo.scala:451: method listAll in class Bar is deprecated: Don't use this method. Its super broken.
[warn]       Bar.listAll(user)

Not only does this warning show us the deprecated method call, it also shows us the file it is being called in and the line number. These warnings are useful for keeping us on the latest APIs and not running afoul of outdated functionality.

Deprecation warnings will be triggered in several circumstances:

  • A method or function that is being used has an @deprecated annotation.
  • A deprecated language feature (e.g., adapted arguments) has been used.

Catching Adapted Arguments

Adapted Arguments are an occasionally frustrating and very much deprecated feature of the Scala language. Essentially, what happens is that the Scala compiler will attempt to automatically convert multiple arguments to a single argument function into a tuple. So having a method foo that takes one argument and invokes it on what appears to be multiple arguments, will result in something unexpected:

scala> def foo[A](bar: A): A = bar
foo: [A](bar: A)A
scala> foo(1,2,3,4)
res1: (Int, Int, Int, Int) = (1,2,3,4)

In the vast majority of cases, this is not how this code is intended to work. To avoid this, we can use either -Yno-adapted-args or -Ywarn-adapted-args to generate an error like the following:

:13: warning: No automatic adaptation here: use explicit parentheses. signature: fooA: A

Catching Numeric Widening

Scala’s weak conformance allows for different number types to “widen” into other numeric types. For example, the result of a numeric operator being applied to an Integer and a Float will be widened into a Float. These conversions are all implicit by default. Sometimes this behavior is not desirable, and instead it is preferable to force the conversion explicitly. By enabling -Ywarn-numeric-widen, the following code will return a warning:

scala> def foo(a: Int): Double = a / 3.0f
:11: warning: implicit numeric widening
       def foo(a: Int): Double = a / 3.0f

The warning can be silenced by explicitly calling toDouble on a.

Value Discarding

Enabling -Ywarn-value-discard can catch subtle bugs around Scala’s Value Discarding feature. By default, if you write a function that has the return type of Unit, but then try to return a value from that function, that value will get thrown away. This feature is especially valuable when the procedure syntax for def is used, whether accidentally or on purpose. One might type the following and expect it to yield a value:

def shouldReturnAValue(a: Int, b: Int) {
  a * b

But actually running this on two numbers will return Unit. This is because the = was not used between the type signature and the function block. With -Ywarn-value-discard enabled, any invocation of shouldReturnAValue will generate a warning that the value a + b is being discarded.

Dead Code Warnings

Sometimes we accidentally define code paths that are impossible to execute. Since this code can never be executed, it’s not desirable to have it remain in the code base. The Scala compiler can help us identify these code paths. When we use the -Ywarn-dead-code compiler option, we can see that the compiler generates warnings for some unreachable pieces of code. In the example below, we can see that using an explicit return (yes I know this isn’t idiomatic Scala) and having code follow it, will produce a warning:

scala> def bar (a: Int): Int = {
     | return 0
     | a + 10
     | }
:12: warning: dead code following this construct
       return 0
bar: (a: Int)Int

Several other code paths can also generate this error:

  •  Unreachable case`s in match statements will generate this (amongst others).
  •  Attempting to throw a value that doesn’t extend Throwable
  •  Attempting to call methods on values that the compiler thinks has a type of Nothing

All of these are probably the result of a bug, and knowing that they are dead code is generally more useful than the errors that occur when this option is not enabled.

Back to the -Xfuture

According to the Scalac documentation, -Xfuture will “Turn on future language features.” I had to dig through the Scala compiler itself to figure out what that meant, since googling for it just returns numerous posts on Scala Futures.

As near as I can tell, -Xfuture currently does five things:

  • Disables adapted arguments by inserting () (which we saw earlier)
  • Disables support of Octal Characters
  • Generates deprecation warnings for View Bounds
  • Generates warnings when using filter instead of withFilter in for comprehensions
  • Disables the assumption that Identifiers and selections in Pattern Matches are unsound (see SI-1503 for more information)

I’d be curious to hear from someone who knows more about this flag. Either way, we have it enabled so we don’t inadvertently use any of the deprecated or disabled features.

Unused Warnings

Some times we define a method or value with the intent of using it, but then never do. These artifacts are usually dead code, and ideally we would like to be able to find them before they are committed and lost forever in source control. Fortunately, by using -Ywarn-unused, we will be informed of certain instances of dead code:

scala> def foo(a: Int): Int = {
     |   val b = a + 4
     |   a
     | }
:12: warning: local val in method foo is never used
         val b = a + 4

One thing to be aware of with this option is that it can produce false positives. The compiler references two issues (SI-7707 and SI-7712) which appear to be fixed. However, these issues reference two other still-open issues: SI-8915 and SI-9158. These issues can cause the -Ywarn-unused to generate false positives. So while this compiler flag can be very beneficial in keeping your code base cleaner, it can also be wrong sometimes. It’s up to you to decide whether enabling this flag is worth it.

Wrapping up

These are some of the basic Scala Compiler options that we have found useful in development. Hopefully, I’ve shown why someone would want to enable them and how they make developing Scala easier. In future posts, I plan to cover language features, linting, and maybe some other random options I find while digging through the compiler.

For More Scala-Related Articles . . .

If you’re interested in other Scala-related articles based on the experiences of Threat Stack developers, have a look at the following: