Some features are dropped to simplify the language. Most of these changes can be handled automatically during the Scala 3 migration compilation.
Incompatibility | Scala 2.13 | Scala 3 Migration Rewrite | Scalafix Rule |
---|---|---|---|
Symbol literals | Deprecation | ✅ | |
do -while construct |
✅ | ||
Auto-application | Deprecation | ✅ | ✅ |
Value eta-expansion | Deprecation | ✅ | ✅ |
any2stringadd conversion |
Deprecation | ✅ | |
Early initializer | Deprecation | ||
Existential type | Feature warning | ||
@specialized | Deprecation |
Symbol literals
The Symbol literal syntax is deprecated in Scala 2.13 and dropped in Scala 3.
But the scala.Symbol
class still exists so that each string literal can be safely replaced by an application of Symbol
.
This piece of code cannot be compiled with Scala 3:
val values: Map[Symbol, Int] = Map('abc -> 1)
val abc = values('abc) // In Scala 3, Migration Warning: symbol literal 'abc is no longer supported
The Scala 3 migration compilation rewrites the code into:
Although the Symbol
class is useful during the transition, beware that it is deprecated and will be removed from the scala-library
in a future version.
You are recommended, as a second step, to replace every use of Symbol
with a plain string literals "abc"
or a custom dedicated class.
do
-while
construct
The do
keyword has acquired a different meaning in the New Control Syntax.
To avoid confusion, the traditional do <body> while (<cond>)
construct is dropped.
It is recommended to use the equivalent while ({ <body>; <cond> }) ()
that can be cross-compiled, or the new Scala 3 syntax while { <body>; <cond> } do ()
.
The following piece of code cannot be compiled with Scala 3.
do { // In Scala 3, Migration Warning: `do <body> while <cond>` is no longer supported
i += 1
} while (f(i) == 0)
The Scala 3 migration compilation rewrites it into.
while ({ {
i += 1
} ; f(i) == 0}) ()
Auto-application
Auto-application is the syntax of calling an empty-paren method such as def toInt(): Int
without passing an empty argument list.
It is deprecated in Scala 2.13 and dropped in Scala 3.
The following code is invalid in Scala 3:
object Hello {
def message(): String = "Hello"
}
println(Hello.message) // In Scala 3, Migration Warning: method message must be called with () argument
The Scala 3 migration compilation rewrites it into:
Auto-application is covered in detail in this page of the Scala 3 reference documentation.
Value eta-expansion
Scala 3 introduces Automatic Eta-Expansion which will deprecate the method to value syntax m _
.
Furthermore Scala 3 does not allow eta-expansion of values to nullary functions anymore.
Thus, this piece of code is invalid in Scala 3:
val x = 1
val f: () => Int = x _ // In Scala 3, Migration Warning: The syntax `<function> _` is no longer supported;
The Scala 3 migration compilation rewrites it into:
any2stringadd
conversion
The implicit Predef.any2stringadd
conversion is deprecated in Scala 2.13 and dropped in Scala 3.
This piece of code does not compile anymore in Scala 3.
val str = new AnyRef + "foo" // In Scala 3, Error: value + is not a member of Object
The conversion to String
must be applied explicitly, for instance with String.valueOf
.
This rewrite can be applied by the fix.scala213.Any2StringAdd
Scalafix rule in scala/scala-rewrites
.
Early Initializer
Early initializers are deprecated in Scala 2.13 and dropped in Scala 3. They were rarely used, and mostly to compensate for the lack of Trait parameters which are now supported in Scala 3.
That is why the following piece of code does not compile anymore in Scala 3.
trait Bar {
val name: String
val size: Int = name.size
}
object Foo extends {
val name = "Foo"
} with Bar
The Scala 3 compiler produces two error messages:
It suggests to use trait parameters which would give us:
trait Bar(name: String) {
val size: Int = name.size
}
object Foo extends Bar("Foo")
Since trait parameters are not available in Scala 2.13, it does not cross-compile.
If you need a cross-compiling solution you can use an intermediate class that carries the early initialized val
s and var
s as constructor parameters.
Another use case for early initializers in Scala 2 is private state in the subclass that is accessed (through an overridden method) by the constructor of the superclass:
class Adder {
var sum = 0
def add(x: Int): Unit = sum += x
add(1)
}
class LogAdder extends {
private var added: Set[Int] = Set.empty
} with Adder {
override def add(x: Int): Unit = { added += x; super.add(x) }
}
This case can be refactored by moving the private state into a nested object
, which is initialized on demand:
Existential Type
Existential type is a dropped feature, which makes the following code invalid.
def foo: List[Class[T]] forSome { type T } // In Scala 3, Error: Existential types are no longer supported
Existential type is an experimental feature in Scala 2.13 that must be enabled explicitly either by importing
import scala.language.existentials
or by setting the-language:existentials
compiler flag.
In Scala 3, the proposed solution is to introduce an enclosing type that carries the dependent type:
Note that using a wildcard argument, _
or ?
, is often simpler but is not always possible.
For instance you could replace List[T] forSome { type T }
by List[?]
.
Specialized
The @specialized
annotation from Scala 2 is ignored in Scala 3.
However, there is limited support for specialized Function
and Tuple
.
Similar benefits can be derived from inline
declarations.