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:
-- [E049] Reference Error: src/main/scala/inheritance-shadowing.scala:9:14
9 | println(x)
| ^
| Reference to x is ambiguous,
| it is both defined in object B
| and inherited subsequently in class C
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:
-- Error: /home/piquerez/scalacenter/scala-3-migration-guide/incompat/access-modifier/src/main/scala-2.13/access-modifier.scala:4:19
4 | private class Bar private[foo] ()
| ^
| non-private constructor Bar in class Bar refers to private class Bar
| in its type signature (): foo.Foo.Bar
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
.
-Foo.curried(1)(true)
+(Foo.apply _).curried(1)(true)
-Foo.tupled((2, false))
+(Foo.apply _).tupled((2, false))
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:
def tuple(location: Location): (Int, Int) = {
- Location.unapply(location).get
+ val Location(lat, lon) = location
+ (lat, lon)
}
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.
val pojo = new Pojo()
-pojo.setFooBar("hello")
+pojo.fooBar = "hello"
-println(pojo.getFooBar())
+println(pojo.fooBar)
=> 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
.
-- [E134] Type Mismatch Error: src/main/scala/by-name-param-type-infer.scala:3:41
3 | val g: (Int, => Int) => Int = Function.uncurried(f)
| ^^^^^^^^^^^^^^^^^^
|None of the overloaded alternatives of method uncurried in object Function with types
| [T1, T2, T3, T4, T5, R]
| (f: T1 => T2 => T3 => T4 => T5 => R): (T1, T2, T3, T4, T5) => R
| [T1, T2, T3, T4, R](f: T1 => T2 => T3 => T4 => R): (T1, T2, T3, T4) => R
| [T1, T2, T3, R](f: T1 => T2 => T3 => R): (T1, T2, T3) => R
| [T1, T2, R](f: T1 => T2 => R): (T1, T2) => R
|match arguments ((Test.f : Int => (=> Int) => Int))
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:
-def f(foo: Foo[_]): Unit
+def f[A](foo: Foo[A]): Unit
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
:
+class FooWrapper[A](foo: Foo[A])
-def g(foos: Seq[Foo[_]]): Unit
+def g(foos: Seq[FooWrapper[_]]): Unit