Some other features are simplified or restricted to make the language easier, safer or more consistent.
Incompatibility | Scala 3 Migration Rewrite |
---|---|
Inheritance shadowing | ✅ |
Non-private constructor in private class | Migration Warning |
Abstract override | |
Case class companion | |
Explicit call to unapply | |
Invisible bean property | |
=>T as type argument |
|
Wildcard type argument |
Inheritance Shadowing
An inherited member, from a parent trait or class, can shadow an identifier defined in an outer scope. That pattern is called inheritance shadowing.
For instance, in this preceding piece of code, the x
term in C can refer to the x
member defined in the outer class B
or it can refer to a x
member of the parent class A
.
You cannot know until you go to the definition of A
.
This is known for being error prone.
That’s why, in Scala 3, the compiler requires disambiguation if the parent class A
does actually have a member x
.
It prevents the following piece of code from compiling.
class A {
val x = 2
}
object B {
val x = 1
class C extends A {
println(x)
}
}
But if you try to compile with Scala 3 you should see an error of the same kind as:
The Scala 3 migration compilation can automatically disambiguate the code by replacing println(x)
with println(this.x)
.
Non-private Constructor In Private Class
The Scala 3 compiler requires the constructor of private classes to be private.
For instance, in the example:
package foo
private class Bar private[foo] () {}
If you try to compile in scala 3 you should get the following error message:
The Scala 3 migration compilation warns about this but no automatic rewrite is provided.
The solution is to make the constructor private, since the class is private.
Abstract Override
In Scala 3, overriding a concrete def with an abstract def causes subclasses to consider the def abstract, whereas in Scala 2 it was considered as concrete.
In the following piece of code, the bar
method in C
is considered concrete by the Scala 2.13 compiler but abstract by the Scala 3 compiler, causing the following error.
trait A {
def bar(x: Int): Int = x + 3
}
trait B extends A {
def bar(x: Int): Int
}
class C extends B // In Scala 3, Error: class C needs to be abstract, since def bar(x: Int): Int is not defined
This behavior was decided in Dotty issue #4770.
An easy fix is simply to remove the abstract def, since in practice it had no effect in Scala 2.
Case Class Companion
The companion object of a case class does not extend any of the Function{0-23}
traits anymore.
In particular, it does not inherit their methods: tupled
, curried
, andThen
, compose
…
For instance, this is not permitted anymore:
case class Foo(x: Int, b: Boolean)
Foo.curried(1)(true)
Foo.tupled((2, false))
A cross-compiling solution is to explicitly eta-expand the method Foo.apply
.
Or, for performance reasons, you can introduce an intermediate function value.
val fooCtr: (Int, Boolean) => Foo = (x, b) => Foo(x, b)
fooCtr.curried(1)(true)
fooCtr.tupled((2, false))
Explicit Call to unapply
In Scala, case classes have an auto-generated extractor method, called unapply
in their companion object.
Its signature has changed between Scala 2.13 and Scala 3.
The new signature is option-less (see the new Pattern Matching reference), which causes an incompatibility when unapply
is called explicitly.
Note that this problem does not affect user-defined extractors, whose signature stays the same across Scala versions.
Given the following case class definition:
The Scala 2.13 compiler generates the following unapply
method:
object Location {
def unapply(location: Location): Option[(Double, Double)] = Some((location.lat, location.long))
}
Whereas the Scala 3 compiler generates:
object Location {
def unapply(location: Location): Location = location
}
Consequently the following code does not compile anymore in Scala 3.
def tuple(location: Location): (Int, Int) = {
Location.unapply(location).get // [E008] In Scala 3, Not Found Error: value get is not a member of Location
}
A possible solution, in Scala 3, is to use pattern binding:
Invisible Bean Property
The getter and setter methods generated by the BeanProperty
annotation are now invisible in Scala 3 because their primary use case is the interoperability with Java frameworks.
For instance, the below Scala 2 code would fail to compile in Scala 3:
class Pojo() {
@BeanProperty var fooBar: String = ""
}
val pojo = new Pojo()
pojo.setFooBar("hello") // [E008] In Scala 3, Not Found Error: value setFooBar is not a member of Pojo
println(pojo.getFooBar()) // [E008] In Scala 3, Not Found Error: value getFooBar is not a member of Pojo
In Scala 3, the solution is to call the more idiomatic pojo.fooBar
getter and setter.
=> T
as Type Argument
A type of the form => T
cannot be used as an argument to a type parameter anymore.
This decision is explained in this comment of the Scala 3 source code.
For instance, it is not allowed to pass a function of type Int => (=> Int) => Int
to the uncurried
method since it would assign => Int
to the type parameter T2
.
The solution depends on the situation. In the given example, you can either:
- define your own
uncurried
method with the appropriate signature - inline the implementation of
uncurried
locally
Wildcard Type Argument
Scala 3 cannot reduce the application of a higher-kinded abstract type member to the wildcard argument.
For instance, the below Scala 2 code would fail to compile in Scala 3:
trait Example {
type Foo[A]
def f(foo: Foo[_]): Unit // [E043] In Scala 3, Type Error: unreducible application of higher-kinded type Example.this.Foo to wildcard arguments
}
We can fix this by using a type parameter:
But this simple solution does not work when Foo
is itself used as a type argument.
def g(foos: Seq[Foo[_]]): Unit
In such case, we can use a wrapper class around Foo
: