stricter Scala with -Xlint, -Xfatal-warnings, and Scalafix
Compile, or compile not. There’s no warning. Two of my favorite Scala compiler flags lately are "-Xlint" and "-Xfatal-warnings".
Here is an example setting that can be used with subprojects:
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.6"
lazy val commonSettings = List(
scalacOptions ++= Seq(
"-encoding", "utf8",
"-deprecation",
"-unchecked",
"-Xlint",
"-feature",
"-language:existentials",
"-language:experimental.macros",
"-language:higherKinds",
"-language:implicitConversions",
"-Ypartial-unification",
"-Yrangepos",
),
scalacOptions ++= (scalaVersion.value match {
case VersionNumber(Seq(2, 12, _*), _, _) =>
List("-Xfatal-warnings")
case _ => Nil
}),
Compile / console / scalacOptions --= Seq("-deprecation", "-Xfatal-warnings", "-Xlint")
)
lazy val foo = (project in file("foo"))
.settings(
commonSettings,
name := "foo",
)
what’s -Xlint?
-Xlint enables a bunch of compiler warnings. @smogami contributed a page called Scala Compiler Options so we can now read what’s in -Xlint.
One of them, for instance is -Xlint:infer-any, which warns when a type argument is inferred to be Any.

-Xfatal-warnings
The problem with warnings is that it often gets postponed and then it piles up. -Xfatal-warnings promotes the warnings to compiler error, so it cannot be ignored.
suppress warnings with silencer
There are situations where warnings are unavoidable. For example, you might need to use a deprecated method for backward compatibility reason. It would be nice if we can suppress warnings for a specific expression.
In 2015 Roman Janusz (@rjghik) wrote a compiler plugin called silencer that does exactly that.
Scala compiler plugin for warning suppression: https://t.co/iPT7AKDq1i
— Roman Janusz (@rjghik) April 14, 2015
The usage looks like this:
import com.github.ghik.silencer.silent
@silent override lazy val ansiCodesSupported = delegate.ansiCodesSupported
This supresses all warnings for the definition.
custom linting using Scalafix
Scalafix is a refactoring and linting tool created by Ólafur (@olafurpg) and others at Scala Center. As the name suggest, it’s good at automated rewrite of code, but recently there’s been more emphasis on using it for linting purpose.
Scalafix 0.8.0-RC1 that came out recently uses Scalameta 4 (well 4.0.0-RC1 to be specific):
Scalafix v0.8.0-RC1 is out with new documentation, improved sbt plugin, better semantic APIs, improved support for custom rules and more https://t.co/sEpy7U9diD
— Ólafur Páll Geirsson (@olafurpg) September 20, 2018
scalafix-noinfer
Previous version of Scalafix shipped with a rule to suppress specific type inference called NoInfer. During recent development it got absorbed by another rule called Disable, which eventually got too complex to be included into Scalafix itself. Instead, Scalafix 0.8 seems to be pursuing the plugin ecosystem route.
Since -Yno-lub hasn’t picked up traction, and I was looking forward to Disable.
So I implemented a Scalafix rule called scalafix-noinfer myself. Here’s how to use it.
project/build.properties
sbt.version=1.2.3
project/plugins.scala
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.8.0-RC1")
build.sbt
ThisBuild / organization := "com.example"
ThisBuild / version := "0.1.0-SNAPSHOT"
ThisBuild / scalaVersion := "2.12.6"
// Scalafix plugin
ThisBuild / scalafixDependencies +=
"com.eed3si9n.fix" %% "scalafix-noinfer" % "0.1.0-M1"
lazy val root = (project in file(".")).
settings(
name := "hello",
addCompilerPlugin(scalafixSemanticdb),
scalacOptions ++= List(
"-Yrangepos",
"-P:semanticdb:synthetics:on",
// you can add the options from the above here too
),
// Compile / scalacOptions += {
// val t = crossTarget.value / "meta"
// s"-P:semanticdb:targetroot:$t"
// },
// Test / scalacOptions += {
// val t = crossTarget.value / "test-meta"
// s"-P:semanticdb:targetroot:$t"
// }
)
.scalafix.conf
rules = [
NoInfer
]
Main.scala
package example
case class Address()
object Main extends App {
List(Animal()).contains("1")
}
scalafix-noinfer usage
From sbt shell type scalafix:
sbt:hello> scalafix
[info] Running scalafix on 2 Scala sources
[error] /Users/eed3si9n/work/quicktest/noinfer/Main.scala:7:3: error: [NoInfer.Serializable] Serializable was inferred, butit's forbidden by NoInfer
[error] List(Animal()).contains("1")
[error] ^^^^^^^^^^^^^^^^^^^^^^^
[error] (Compile / scalafix) scalafix.sbt.ScalafixFailed: LinterError
Yes! So now we have NoInfer rule that’s catching bad type inference in contains(...). In my opinion, it doesn’t make sense for Scala to lub to java.io.Serializable since the list would never contain "1".
By default this rule forbids the inference of scala.Any, scala.AnyVal, java.io.Serializable, scala.Serializable, and scala.Product. You can customize this using .scalafix.conf as follows:
rules = [
NoInfer
]
NoInfer.disabledTypes = [
scala.Any,
scala.AnyVal,
scala.Serializable,
java.io.Serializable,
scala.Product,
scala.Predef.any2stringadd
]
Now this will catch scala.Predef.any2stringadd:
[info] Running scalafix on 2 Scala sources
[error] /Users/eed3si9n/work/quicktest/noinfer/Main.scala:8:3: error: [NoInfer.any2stringadd] any2stringadd was inferred, but it's forbidden by NoInfer
[error] Option(1) + "what"
[error] ^^^^^^^^^
[error] (Compile / scalafix) scalafix.sbt.ScalafixFailed: LinterError
challenges
First issue that I noticed is that I can’t seem to move the targetroot of semanticdb. This means semanticdb will be shipped with your JAR if you use Scalafix with semantic rules. I should be able to opt out of this. Maybe I need to dig deeper to find out how.
scalafix-noinfer is a progress forward, and it’s more usable than a forked Scala compiler, but it’s not as thorough as -Yno-lub. For instance, it seems to be perfectly ok with the following:
object Main extends App {
val x = if (true) 1 else false
val y = 1 match { case 1 => Array(1); case n => Vector(n) }
}
summary
-Xlintand-Xfatal-warningsprovide stronger enforcement against common mistakes.- When we need to bail out some code, we can use
@silentannotation. - Scalafix allows flexible linting that can be extended through custom rules.