Over a million developers have joined DZone.

Useful Scala Compiler Options (Part 3)

The third part in the series on Scala compilers focuses on -Xlint. See how it can help improve your code and avoid funky code smells.

· Java Zone

Microservices! They are everywhere, or at least, the term is. When should you use a microservice architecture? What factors should be considered when making that decision? Do the benefits outweigh the costs? Why is everyone so excited about them, anyway?  Brought to you in partnership with IBM.

In my previous two posts on Scala Compiler options, we saw a number of options that can improve your experience developing Scala. In this post I want to focus on one option in particular:
-Xlint. If you thought the other options made your life better, this one will improve it by leaps and bounds.

Image title


-Xlint enables a number of linting options in the Scala compiler that will produce warnings about various code smells. There are 15 linting options in Scala 2.11. By default, they are disabled, but can all be enabled by adding -Xlint to your scalacOptions setting. They can also be enabled individually by using -Xlint:<option>. Most of the resources I've seen suggest enabling all of the options and calling it a day. This is good advice and you should probably follow it, but I wanted to take some time to dig into what you are actually getting when you enable this option. In addition, it's very helpful to see the linting warnings to understand what issue the compiler has surfaced and why it was produced.  

Adapted Args

The Scala compiler can attempt to adapt a set of arguments into a tuple. This can lead to an issue where a tuple is passed to a parameterized function that takes a single argument. There are several ways to catch adapted arguments, as I've mentioned in previous blog posts, but this is yet another way to handle it. As we have seen before, this warning can be triggered by trying to pass a tuple to a single argument constructor:

def insert[A](value: A): Long = ??? 

insert(1,2,3,4) 

Nullary Unit

In Scala, functions that take no arguments (nullary functions) can be defined in one of two ways:

def executeQuery: Int 
def executeQuery(): Unit

However, the recommended style is to use the parenthesis when the function produces a side effect. So a code block like the following will trigger the Nullary Unit warning:

def executeQuery: Unit = () 
warning: side-effecting nullary methods are discouraged: suggest defining as `def nullary()` instead 
def executeQuery: Unit = () 
^

Nullary Override

Nullary methods can cause us more pain, because methods defined without parenthesis are actually considered different methods signatures:

trait Row {
  def columns: Int = ???
}

class PGRow extends Row {
  override def columns(): Int = ???
}

warning: non-nullary method overrides nullary method
  override def columns(): Int = ???

This can also be a pain point when trying to interoperate with Java. If we had a Java class that inherited from Row in the previous example, we would also have this issue.  

Inaccessible

Due to Scala’s scoping rules, it is possible to define classes and methods in such a way that they are impossible to override because their arguments are private:

object Database {
  private class DatabaseConnection
  class DatabaseConnectionPool { 
    def apply(conn: DatabaseConnection): DatabaseConnectionPool = ??? 
   }
}

warning: method apply in class DatabaseConnectionPool references private class DatabaseConnection.
Classes which cannot access DatabaseConnection may be unable to override apply.
       def apply(conn: DatabaseConnection): DatabaseConnectionPool = ???
           ^

In this example, the DatabaseConnection is exposed in the DatabaseConnectionPool’sapply method, but it is private to the Database object. While this is valid Scala code, it can be a code smell because if we try to extend DatabaseConnectionPool we will be unable to.

Infer Any

When we let the compiler infer the types of our values, we can sometimes get surprising results. In the worst case, the compiler can’t infer anything about the value that we gave it and must resolve it to Any. In the vast majority of cases, we don’t want our values to just be Any, so it would be helpful if the compiler would warn us when a value was inferred to be Any.

val values = Seq(4, 4, "", 'g)

warning: a type was inferred to be `Any`; this may indicate a programming error.
       val values = Seq(4, 4, "", 'g)

Missing Interpolator

Scala offers us string interpolation through StringContext. An example of basic interpolation is `s"$x is a number"`. Sometimes, we forget to add the interpolator:

def displayRow(row: Row) = "$row"
warning: possible missing interpolator: detected interpolated identifier `$row`
       def displayRow(row: Row) = "$row"
             ^

In this case, we can get an error warning us that we probably forgot s. Note that this warning will not trigger if there is no variable in scope that comes after $ in the string:

def displayRow(row: Row) = "$table"

Doc Detached

I find this option to be quite puzzling because, try as I might, I cannot trigger it. At first glance, its name suggests that it will warn that you have free hanging documentation blocks; however, this is not the case. I looked at the source code of the compiler and found a comment saying that it will “only warn in the presence of suspicious tags that appear to be documenting API.” Basically, this option is supposed to warn you if you have free-hanging API documentation. Despite being able to see the heuristic for what constitutes API documentation, I still cannot produce the warning from this option. If anyone has found this option useful in the past, I’d be interested to hear about your experience with it. 

Private Shadow

Sometimes we have to deal with mutable variables. The linter can provide some protection from shadowing names of mutable variables such that they would cause the variable from a parent class to not receive the assignment. In this case, assigning to conn would change in PGDatabase but not in Database:

class Database {
  val conn: Connection = ???
}

class PGDatabase extends Database {
  private[this] val conn: Connection= ???

  def updateConnection(connection: Connection) = conn = connection
}

The linter can warn us of this:

private[this] variable conn in class PGDatabase shadows mutable conn inherited from class Database.  Changes to conn will not be visible within class PGDatabase - you may want to give them distinct names.
    conn = connection
    ^

Type Parameter Shadow

Similar to the private shadowing issue, type parameters can be shadowed as well. It is unlikely that you want to shadow a type parameter (although you can), so this lint warning will prevent you from writing code like the following:

class Table[T] {
  def column[T](a: String): T = ???
}
:12: warning: type parameter T defined in method bar shadows type T defined in class Table. You may want to rename your type parameter, or possibly remove it.
         def column[T](a: String): T = ???
               ^
defined class Table

In this case, we defined the type parameter on the class and then redefined it on the method.

Poly Implicit Overload

This warning is less relevant these days, as View Bounds have been deprecated for a while, and as far as I’ve seen, not really used anyway. The gist of the error is that if you have two implicit definitions and one of them is parameterized and you then attempt to use the parameterized definition in a View Bound, the Scala compiler will emit an error that looks something like this:

parameterized overloaded implicit methods are not visible as view bounds

Option Implicit

In Scala, we make use of implicits, sometimes without being fully aware of what we are relying on. Often the only reason we are able to treat one value as another is the result of an implicit that we were unaware of. This can be especially problematic when converting from Java classes to Scala classes that have an implicit conversion in the Predef:

case class Row(name: String, value: Option[Double]) {
    def this(value: java.lang.Double) = this("field", Option(value))
}

This compiles because Scala's Predef defines implicit conversions from Java's String and Double to Scala's String and Double. However, there is a problem. If value is passed into the auxiliary constructor as null, then a Null Pointer Exception will be raised. To warn of these implicit conversions being applied when creating an Option, we can use option-implicit to generate this warning:

warning: Suspicious application of an implicit view (scala.this.Predef.Double2double) in the argument to Option.apply.
       def this(value: java.lang.Double) = this("field", Option(value))

Delayed Init Select

Scala’s DelayedInit delays the initialization of code in a class or object body. Although it is now deprecated, it still appears regularly enough in Scala applications in the form of the App trait. Problems can occur when using values that are exposed from classes of objects from outside of that scope:

object DataBaseConnection {
    val maxConnections = Main.maxConnections
}

object Main extends App {
    val maxConnections = 5
}

Main.scala:2: warning: Selecting value maxConnections from object Main, which extends scala.DelayedInit, is likely to yield an uninitialized value
    val maxConnections = Main.maxConnections

As the error indicates, it is possible that Main.maxConnections has not been initialized when it is being used inside DataBaseConnection.

By Name Right Associative

Sometimes some of Scala’s more powerful features don’t always play well together. One example is trying to use a right associative method with a by-name parameter:

class Database {
    def >>:(x: => Row): DatabaseId = ???
}

warning: by-name parameters will be evaluated eagerly when called as a right-associative infix operator. For more details, see SI-1980.
         def >>:(x: => Row): DatabaseId = ???

As we can see, due to a compiler bug, we cannot have the argument be both a by-name parameter and a right associative function. If you want to learn more about why this is, take a look at SI-1980.

Package Object Classes

This warning might seem a little odd at first. It will raise a warning when you have defined a class inside a Package Object:

package object db {
    case class DbValue()
}

warning: it is not recommended to define classes/objects inside of package objects.
If possible, define class DbValue in package db instead.

While it might seem no different than defining DbValue in the db package, it is actually discouraged because it can be problematic for the compiler. In addition, there is the chance of naming collisions between projects under the same package name that depend on each other.

Unsound Match

The unsound match option is hugely beneficial especially if you find yourself using more complicated match statements. In many cases, the compiler can determine if your match statement has holes in it that could cause a value to fall through and cause a MatchError. For example, if you match on an Option type, and only have a branch for Some, the compiler can warn you:

def parseResultSet(res: Option[Int]): Int = res match {
    case Some(v) => v
}

warning: match may not be exhaustive.
It would fail on the following input: None
       def parseResultSet(res: Option[Int]): Int = res match {
                                      ^

However, this will not catch logic errors, so it is still possible to write match statements that cause errors:

def parseResultSet(y: Option[Int]): Int = y match {
    case Some(v) if v < 0 => v
    case None => 0
}

This block of code will throw an error when Some(1) is passed in, but this cannot be caught by the linter.

Stars Align

In addition to matches being unsound, it is also possible to pattern match case classes with variable arguments in such a way that is valid code, but can result in odd errors. The following example shows two cases in which this can happen:

trait Row
case class Insert(table: String, rows: Row*)
def insert(i: Insert) = i match {
    case Insert(table, rows) =>
    case Insert(table, head, tail @ _*) =>
}
:17: warning: A repeated case parameter or extracted sequence is not matched by a sequence wildcard (_*), and may fail at runtime.
         case Insert(table, rows) =>
                 ^
:18: warning: Sequence wildcard (_*) does not align with repeated case parameter or extracted sequence; the result may be unexpected.
         case Insert(table, head, tail @ _*) =>
                 ^

In the first case, the rows may not match depending on the number of elements in that field. In the second case, we try to capture the head and the tail of the sequence, which can also fail to match at runtime.  

Wrapping Up

Hopefully this deep dive into -Xlint has given you a better understanding of a number of code smells that can crop up in Scala and how to catch them at compile time. Armed with this knowledge, you should experience a significant improvement in your ability to develop in Scala.

Discover how the Watson team is further developing SDKs in Java, Node.js, Python, iOS, and Android to access these services and make programming easy. Brought to you in partnership with IBM.

Topics:
scala ,compilers ,java ,xlint

Published at DZone with permission of Ryan Plessner, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}