Built-in Warts
Here is a list of built-in warts under the
org.wartremover.warts
package.
ArrayEquals
Unlike other collections ==
on arrays and iterators checks reference equality:
List(1) == List(1) //true
Array(1) == Array(1) //false, won't compile: == is disabled, use sameElements instead
Any
Any
is the top type; it is the supertype of every other type. The
Scala compiler loves to infer Any
as a generic type, but that is
almost always incorrect. Explicit type arguments should be used
instead.
// Won't compile: Inferred type containing Any
val any = List(1, true, "three")
AnyVal
See Any
.
// Won't compile: Inferred type containing AnyVal
val xs = List(1, true)
AsInstanceOf
asInstanceOf
is unsafe in isolation and violates parametricity when guarded by isInstanceOf
. Refactor so that the desired type is proven statically.
// Won't compile: asInstanceOf is disabled
x.asInstanceOf[String]
DefaultArguments
Scala allows methods to have default arguments, which make it hard to use methods as functions.
// Won't compile: Function has default arguments
def x(y: Int = 0)
EitherProjectionPartial
scala.util.Either.LeftProjection
and scala.util.Either.RightProjection
have a get
method which will throw if the value doesn’t match the
projection. The program should be refactored to use scala.util.Either.LeftProjection#toOption
and scala.util.Either.RightProjection#toOption
to explicitly handle both
the Some
and None
cases.
Enumeration
Scala’s Enumeration
can cause performance problems due to its reliance on reflection. Additionally, the lack of exhaustive match checks and partial methods can lead to runtime errors. Instead of Enumeration
, a sealed abstract class extended by case objects should be used instead.
Equals
Scala’s Any
type provides an ==
method which is not type-safe. Using this method allows obviously incorrect code like 5 == "5"
to compile. A better version which forbids equality checks across types (which always fail) is easily defined:
@SuppressWarnings(Array("org.wartremover.warts.Equals"))
implicit final class AnyOps[A](self: A) {
def ===(other: A): Boolean = self == other
}
equals
, eq
, ne
are disabled as well.
ExplicitImplicitTypes
Scala has trouble correctly resolving implicits when some of them lack explicit result types. To avoid this, all implicits should have explicit type ascriptions.
FinalCaseClass
Scala’s case classes provide a useful implementation of logicless data types. Extending a case class can break this functionality in surprising ways. This can be avoided by always making them final or sealed.
// Won't compile: case classes must be final
case class Foo()
FinalVal
Value of a final val
is inlined and can cause inconsistency during incremental compilation (see sbt/sbt/issues/1543 ).
file 1:
object c {
// Won't compile: final val is disabled
final val v = 1
}
file 2:
println(c.v)
ImplicitConversion
Implicit conversions weaken type safety and always can be replaced by explicit conversions.
// Won't compile: implicit conversion is disabled
implicit def int2Array(i: Int) = Array.fill(i)("madness")
ImplicitParameter
Implicit parameters as configuration often lead to confusing interfaces and can result in surprising inconsistencies.
// Won't compile: Implicit parameters are disabled
def f()(implicit s: String) = ()
// Still compiles
def f[A: Ordering](a: A, other: A) = ...
def f(a: A, other: A)(implicit ordering: Ordering[A]) = ...
IsInstanceOf
isInstanceOf
violates parametricity. Refactor so that the type is established statically.
// Won't compile: isInstanceOf is disabled
x.isInstanceOf[String]
JavaConversions
The standard library provides implicits conversions to and from Java types in scala.collection.JavaConversions
. This can make code difficult to understand and read about. The explicit conversions provided by scala.collection.JavaConverters
should be used instead.
// Won't compile: scala.collection.JavaConversions is disabled
import scala.collection.JavaConversions._
val scalaMap: Map[String, String] = Map()
val javaMap: java.util.Map[String, String] = scalaMap
JavaSerializable
java.io.Serializable
is a common subtype to many structures, especially those
imported from Java. For example, String is a subtype of java.io.Serializable
but not scala.Serializable
. The Scala compiler loves to infer
java.io.Serializable
as a common supertype, but that is almost always
incorrect. Explicit type arguments should be used instead.
// Won't compile: Inferred type containing java.io.Serializable
object O extends Serializable
val mistake = List("foo", "bar", O /* forgot O.toString */)
LeakingSealed
Descendants of a sealed type must be final or sealed. Otherwise this type can be extended in another file through its descendant.
file 1:
// Won't compile: Descendants of a sealed type must be final or sealed
sealed trait t
class c extends t
file 2:
class d extends c
MutableDataStructures
The standard library provides mutable collections. Mutation breaks equational reasoning.
// Won't compile: scala.collection.mutable package is disabled
import scala.collection.mutable.ListBuffer
val mutList = ListBuffer()
NonUnitStatements
Scala allows statements to return any type. Statements should only
return Unit
(this ensures that they’re really intended to be
statements).
// Won't compile: Statements must return Unit
10
false
When interfacing with APIs that violate this principle, one can define a function in the project
@specialized def discard[A](evaluateForSideEffectOnly: A): Unit = {
val _: A = evaluateForSideEffectOnly
() //Return unit to prevent warning due to discarding value
}
discard{ badMutateFun(foo) } // Use like this
Nothing
Nothing
is a special bottom type; it is a subtype of every other
type. The Scala compiler loves to infer Nothing
as a generic type but
that is almost always incorrect. Explicit type arguments should be
used instead.
// Won't compile: Inferred type containing Nothing
val nothing = ???
val nothingList = List.empty
Null
null
is a special value that inhabits all reference types. It breaks
type safety.
// Won't compile: null is disabled
val s: String = null
var s2: String = _
Option2Iterable
Scala inserts an implicit conversion from Option
to Iterable
. This can hide bugs and creates surprising situations like Some(1) zip Some(2)
returning an Iterable[(Int, Int)]
.
OptionPartial
scala.Option
has a get
method which will throw if the value is
None
. The program should be refactored to use scala.Option#fold
to
explicitly handle both the Some
and None
cases.
Overloading
Method overloading may lead to confusion and usually can be avoided.
// Won't compile: Overloading is disabled
class c {
def equals(x: Int) = {}
}
Product
Product
is a type common to many structures; it is the supertype of
case classes and tuples. The Scala compiler loves to infer Product
as
a generic type, but that is almost always incorrect. Explicit type
arguments should be used instead.
// Won't compile: Inferred type containing Product
val any = List((1, 2, 3), (1, 2))
PublicInference
Type inference of public members can expose extra type information, that can break encapsulation.
class c {
// Won't compile: Public member must have an explicit type ascription
def f() = new c with t
val name = "abc" // Compiles: fields initialized by string, char or boolean literals are ignored
}
class c2 extends c {
override def f() = ... // Compiles: overridden members are ignored
}
Recursion
General recursion can result in non-termination. There are various techniques, like fixed-point combinators, that allow you to extract recursion from your code. Recursion can also cause problems with stack usage. This can often be fixed with a @tailrec
annotation (which uses constant stack) or by using a trampoline (which moves stack usage to the heap).
// Won't compile: Potentially-diverging recursion.
def diverging(i: Int): Int = if (i == 0) 0 else diverging(i + 1)
This particular instance can be silenced by adding @tailrec
before def
, making the stack safety explicit.
Return
return
breaks referential transparency. Refactor to terminate computations in a safe way.
// Won't compile: return is disabled
def foo(n:Int): Int = return n + 1
def foo(ns: List[Int]): Any = ns.map(n => return n + 1)
Serializable
Serializable
is a type common to many structures. The Scala compiler
loves to infer Serializable
as a generic type, but that is almost
always incorrect. Explicit type arguments should be used instead.
// Won't compile: Inferred type containing Serializable
val any = List((1, 2, 3), (1, 2))
StringPlusAny
Scala’s String
interface provides a +
method that converts the operand to a String
via its toString
method. As mentioned in the documentation for the ToString
wart, this method is unreliable and brittle.
// Won't compile: Implicit conversion to string is disabled
"foo" + {}
{} + "bar"
Throw
throw
implies partiality. Encode exceptions/errors as return
values instead using Either
.
ToString
Scala creates a toString
method automatically for all classes. Since toString
is based on the class name, any rename can potentially introduce bugs. This is especially pernicious for case objects. toString
should be explicitly overridden wherever used.
case object Foo { override val toString = "Foo" }
IterableOps
scala.collection.Iterable
has:
head
,tail
,init
,last
,reduce
,reduceLeft
,reduceRight
,max
,maxBy
,min
andminBy
methods,
all of which will throw if the collection is empty. The program should be refactored to use:
headOption
,drop(1)
,dropRight(1)
,lastOption
,reduceOption
orfold
,reduceLeftOption
orfoldLeft
andreduceRightOption
orfoldRight
respectively,
to explicitly handle empty collections.
TripleQuestionMark
???
throws NotImplementedError
. Encode exceptions/errors as return values instead using Either
.
// Won't compile: ??? is disabled
def foo: Int = ???
TryPartial
scala.util.Try
has a get
method which will throw if the value is a
Failure
. The program should be refactored to use scala.util.Try#map
and scala.util.Try#getOrElse
to
explicitly handle both the Success
and Failure
cases.
Unsafe
Checks for the following warts:
- Any
- AsInstanceOf
- DefaultArguments
- EitherProjectionPartial
- IsInstanceOf
- IterableOps
- NonUnitStatements
- Null
- OptionPartial
- Product
- Return
- Serializable
- StringPlusAny
- Throw
- TripleQuestionMark
- TryPartial
- Var
This list is built from Unsafe.scala
Var
Mutation breaks equational reasoning.
// Won't compile: var is disabled
var x = 100
While
while
loop usually indicates low-level code. If performance is not an issue, it can be replaced.
// Won't compile: while is disabled
while(i < 10) {
i += 1
...
}