Skip to content

Commit

Permalink
finish migration to code tabs in migration guide
Browse files Browse the repository at this point in the history
  • Loading branch information
bishabosha committed Jun 25, 2024
1 parent 005ae21 commit 408e8bb
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 26 deletions.
45 changes: 28 additions & 17 deletions _overviews/scala3-migration/incompat-type-inference.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,15 @@ The new algorithm is better than the old one, but sometime it can fail where Sca

> It is always good practice to write the result types of all public values and methods explicitly.
> It prevents the public API of your library from changing with the Scala version, because of different inferred types.
>
>
> This can be done prior to the Scala 3 migration by using the [ExplicitResultTypes](https://scalacenter.github.io/scalafix/docs/rules/ExplicitResultTypes.html) rule in Scalafix.
## Return Type of an Override Method

In Scala 3 the return type of an override method is inferred by inheritance from the base method, whereas in Scala 2.13 it is inferred from the left hand side of the override method.

```scala
class Parent {
def foo: Foo = new Foo
}

class Child extends Parent {
override def foo = new RichFoo(super.foo)
}
```

In this example, `Child#foo` returns a `RichFoo` in Scala 2.13 but a `Foo` in Scala 3.
It can lead to compiler errors as demonstrated below.

{% tabs define_parent_child %}
{% tab 'Scala 2 and 3' %}
```scala
class Foo

Expand All @@ -48,13 +37,24 @@ class Parent {
class Child extends Parent {
override def foo = new RichFoo(super.foo)
}
```
{% endtab %}
{% endtabs %}

In this example, `Child#foo` returns a `RichFoo` in Scala 2.13 but a `Foo` in Scala 3.
It can lead to compiler errors as demonstrated below.

{% tabs extend_parent_child %}
{% tab 'Scala 3 Only' %}
```scala
(new Child).foo.show // Scala 3 error: value show is not a member of Foo
```
{% endtab %}
{% endtabs %}

In some rare cases involving implicit conversions and runtime casting it could even cause a runtime failure.

The solution is to make the return type of the override method explicit:
The solution is to make the return type of the override method explicit so that it matches what is inferred in 2.13:

{% highlight diff %}
class Child extends Parent {
Expand All @@ -68,19 +68,30 @@ class Child extends Parent {
Scala 2 reflective calls are dropped and replaced by the broader [Programmatic Structural Types]({{ site.scala3ref }}/changed-features/structural-types.html).

Scala 3 can imitate Scala 2 reflective calls by making `scala.reflect.Selectable.reflectiveSelectable` available wherever `scala.language.reflectiveCalls` is imported.
However the Scala 3 compiler does not infer structural types by default, and thus fails at compiling:

{% tabs define_structural %}
{% tab 'Scala 2 and 3' %}
```scala
import scala.language.reflectiveCalls

val foo = new {
def bar: Unit = ???
}
```
{% endtab %}
{% endtabs %}

However the Scala 3 compiler does not infer structural types by default, and so infers type `Object` for `foo`, rather than `{ def bar: Unit }` as in Scala 2.13, so the following structural selection fail to compile:

{% tabs use_structural %}
{% tab 'Scala 3 Only' %}
```scala
foo.bar // Error: value bar is not a member of Object
```
{% endtab %}
{% endtabs %}

The straightforward solution is to write down the structural type.
The straightforward solution is to explicitly write down the structural type.

{% highlight diff %}
import scala.language.reflectiveCalls
Expand Down
54 changes: 50 additions & 4 deletions _overviews/scala3-migration/plugin-kind-projector.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ next-page: external-resources

In the future, Scala 3 will use the `_` underscore symbol for placeholders in type lambdas---just as the underscore is currently used for placeholders in (ordinary) term-level lambdas.

The new type lambda syntax is not enabled by default, to enable it, use a compiler flag `-Ykind-projector:underscores`. Note that enabling underscore type lambdas will disable usage of `_` as a wildcard, you will only be able to write wildcards using the `?` symbol.
The new type lambda syntax is not enabled by default, to enable it, use a compiler flag `-Ykind-projector:underscores`. Note that enabling underscore type lambdas will disable usage of `_` as a wildcard, you will only be able to write wildcards using the `?` symbol.

If you wish to cross-compile a project for Scala 2 & Scala 3 while using underscore type lambdas for both, you may do so starting with [kind-projector](https://github.com/typelevel/kind-projector) version `0.13.0` and up and Scala 2 versions `2.13.6` and `2.12.14`.
To enable it, add the compiler flags `-Xsource:3 -P:kind-projector:underscore-placeholders` to your build.
As in Scala 3, this will disable usage of `_` as a wildcard, however, the flag `-Xsource:3` will allow you to replace it with the `?` symbol.
As in Scala 3, this will disable usage of `_` as a wildcard, however, the flag `-Xsource:3` will allow you to replace it with the `?` symbol.

The following `sbt` configuration will set up the correct flags to cross-compile with new syntax:

Expand All @@ -34,43 +34,65 @@ In turn, you will also have to rewrite all usages of `_` as the wildcard to use

For example the following usage of the wildcard:

{% tabs wildcard_scala2 %}
{% tab 'Scala 2 Only' %}
```scala
def getWidget(widgets: Set[_ <: Widget], name: String): Option[Widget] = widgets.find(_.name == name)
def getWidget(widgets: Set[_ <: Widget], name: String): Option[Widget] =
widgets.find(_.name == name)
```
{% endtab %}
{% endtabs %}

Must be rewritten to:

{% tabs wildcard_scala3 %}
{% tab 'Scala 3 Only' %}
```scala
def getWidget(widgets: Set[? <: Widget], name: String): Option[Widget] = widgets.find(_.name == name)
def getWidget(widgets: Set[? <: Widget], name: String): Option[Widget] =
widgets.find(_.name == name)
```
{% endtab %}
{% endtabs %}

And the following usages of kind-projector's `*` placeholder:

{% tabs kind_projector_scala2 %}
{% tab 'Scala 2 Only' %}
```scala
Tuple2[*, Double] // equivalent to: type R[A] = Tuple2[A, Double]
Either[Int, +*] // equivalent to: type R[+A] = Either[Int, A]
Function2[-*, Long, +*] // equivalent to: type R[-A, +B] = Function2[A, Long, B]
```
{% endtab %}
{% endtabs %}

Must be rewritten to:

{% tabs kind_projector_scala3 %}
{% tab 'Scala 3 Only' %}
```scala
Tuple2[_, Double] // equivalent to: type R[A] = Tuple2[A, Double]
Either[Int, +_] // equivalent to: type R[+A] = Either[Int, A]
Function2[-_, Long, +_] // equivalent to: type R[-A, +B] = Function2[A, Long, B]
```
{% endtab %}
{% endtabs %}

## Compiling Existing Code

Even without migrating to underscore type lambdas, you will likely be able to compile most of it with Scala 3 without changes.

Use the flag `-Ykind-projector` to enable support for `*`-based type lambdas (without enabling underscore type lambdas), the following forms will now compile:

{% tabs kind_projector_cross %}
{% tab 'Scala 2 and 3' %}
```scala
Tuple2[*, Double] // equivalent to: type R[A] = Tuple2[A, Double]
Either[Int, +*] // equivalent to: type R[+A] = Either[Int, A]
Function2[-*, Long, +*] // equivalent to: type R[-A, +B] = Function2[A, Long, B]
```
{% endtab %}
{% endtabs %}

## Rewriting Incompatible Constructs

Expand All @@ -82,6 +104,8 @@ Scala 3's `-Ykind-projector` & `-Ykind-projector:underscores` implement only a s

You must rewrite ALL of the following forms:

{% tabs kind_projector_illegal_scala2 %}
{% tab 'Scala 2 Only' %}
```scala
// classic
EitherT[*[_], Int, *] // equivalent to: type R[F[_], B] = EitherT[F, Int, B]
Expand All @@ -92,36 +116,58 @@ EitherT[_[_], Int, _] // equivalent to: type R[F[_], B] = EitherT[F, Int, B]
// named Lambda
Lambda[(F[_], A) => EitherT[F, Int, A]]
```
{% endtab %}
{% endtabs %}

Into the following long-form to cross-compile with Scala 3:

{% tabs kind_projector_illegal_cross %}
{% tab 'Scala 2 and 3' %}
```scala
type MyLambda[F[_], A] = EitherT[F, Int, A]
MyLambda
```
{% endtab %}
{% endtabs %}

Alternatively you may use Scala 3's [Native Type Lambdas]({{ site.scala3ref }}/new-types/type-lambdas.html) if you do not need to cross-compile:

{% tabs kind_projector_illegal_scala3 %}
{% tab 'Scala 3 Only' %}
```scala
[F[_], A] =>> EitherT[F, Int, A]
```
{% endtab %}
{% endtabs %}

For `Lambda` you must rewrite the following form:

{% tabs kind_projector_illegal_lambda_scala2 %}
{% tab 'Scala 2 Only' %}
```scala
Lambda[(`+E`, `+A`) => Either[E, A]]
```
{% endtab %}
{% endtabs %}

To the following to cross-compile:

{% tabs kind_projector_illegal_lambda_cross %}
{% tab 'Scala 2 and 3' %}
```scala
λ[(`+E`, `+A`) => Either[E, A]]
```
{% endtab %}
{% endtabs %}

Or alternatively to Scala 3 type lambdas:

{% tabs kind_projector_illegal_lambda_scala3 %}
{% tab 'Scala 3 Only' %}
```scala
[E, A] =>> Either[E, A]
```
{% endtab %}
{% endtabs %}

Note: Scala 3 type lambdas no longer need `-` or `+` variance markers on parameters, these are now inferred.
8 changes: 4 additions & 4 deletions _overviews/scala3-migration/tutorial-macro-cross-building.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,11 +101,11 @@ If you try to compile with Scala 3 you should see some errors of the same kind a
{% highlight text %}
sbt:example> ++3.3.1
sbt:example> example / compile
[error] -- Error: /example/src/main/scala/location/Location.scala:15:35
[error] -- Error: /example/src/main/scala/location/Location.scala:15:35
[error] 15 | val location = typeOf[Location]
[error] | ^
[error] | No TypeTag available for location.Location
[error] -- Error: /example/src/main/scala/location/Location.scala:18:4
[error] -- Error: /example/src/main/scala/location/Location.scala:18:4
[error] 18 | q"new $location($path, $line)"
[error] | ^
[error] |Scala 2 macro cannot be used in Dotty. See https://dotty.epfl.ch/docs/reference/dropped-features/macros.html
Expand Down Expand Up @@ -165,7 +165,7 @@ They must have the exact same signature than their Scala 2.13 counterparts.
package location

object Macros:
def location: Location = ???
inline def location: Location = ???
```
{% endtab %}
{% endtabs %}
Expand All @@ -191,7 +191,7 @@ object Macros:
private def locationImpl(using quotes: Quotes): Expr[Location] =
import quotes.reflect.Position
val pos = Position.ofMacroExpansion
val file = Expr(pos.sourceFile.jpath.toString)
val file = Expr(pos.sourceFile.path.toString)
val line = Expr(pos.startLine + 1)
'{new Location($file, $line)}
```
Expand Down
2 changes: 1 addition & 1 deletion _overviews/scala3-migration/tutorial-macro-mixing.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ previous-page: tutorial-macro-mixing
next-page: tooling-syntax-rewriting
---

This tutorial shows how to mix Scala 2.13 and Scala 3 macros in a single artifact. This means that consumers can use '-Ytasty-reader' from Scala 2.13 code that uses your macros.
This tutorial shows how to mix Scala 2.13 and Scala 3 macros in a single artifact. This means that consumers can use `-Ytasty-reader` from Scala 2.13 code that uses your macros.

There are two main benefits of this:

Expand Down

0 comments on commit 408e8bb

Please sign in to comment.