Tooling for scala 3

Dotty support in Metals

Tomasz Godzik

Some fun new concepts

 

Besides:

- simplified pattern matching

- better macros

- enums

- focus on ease of migration

- implicit suggestions

- a lot of even more modern features

 

THE FUTURE OR SCALA 3

trait A:
  def f: Int

class C(x: Int) extends A:
  def f = x

object O:
  def f = 3

Optional braces

val x: String = ???
val y: String | Null = ???

x == null       // error
x eq null       // error
"hello" == null // error

y == null       // ok
y == x          // ok

explicit nulls

@main def helloWorld(
  age: Int, 
  name: String, 
  others: String*
) = {
  println(s"Hello $name!")
  println(s"You are $age years old")
}

outer methods

case class Circle(
  x: Double, 
  y: Double, 
  radius: Double
)

def (c: Circle).circumference: Double = 
  c.radius * math.Pi * 2

Extension methods

scalameta parser

- basis for a number of developer's tools

 

- works without the compiler

 

- fast

 

- has an easily understandable AST

 

- a lot projects depending on it

extension (c: Circle) 
   def shapeName = "Circle"
Source(
  stats = List(
    Defn.ExtensionGroup(
      eparam = Term.Param(
        mods = List(),
        name = Term.Name(value = "c"),
        decltpe = Some(value = Type.Name(value = "Circle")),
        default = None
      ),
      tparams = List(),
      uparams = List(),
      body = Defn.Def(
        mods = List(),
        name = Term.Name(value = "shapeName"),
        tparams = List(),
        paramss = List(),
        decltpe = None,
        body = Lit.String(value = "Circle")
      )
    )
  )
)

scalameta parser

enum ListEnum {
    enum ListEnum[+A] {
    case Cons(h: A, t: ListEnum[A])
    case Empty
  }
}

Document outline

Worksheets

scalameta parser

Parser Scala when used with the scala3 dialect

optional braces support

all the new features supported

scalameta parser

scala.meta.parser.dotty.CommunityDottySuite:
From https://github.com/lampepfl/dotty
   a0707985bf..1f503e7868  master        -> origin/master
   e8cfbb381f..45b8dc0f9e  release-3.0.0 -> origin/release-3.0.0
HEAD is now at 13de192d04 Merge pull request #11958 from lampepfl/derived-names-mac
--------------------------
dotty
Files parsed correctly 828
Files errored: 0
Time taken: 14989ms
Lines parsed: ~190k
Parsing speed per 1k lines ===> 78 ms/1klines
--------------------------
  + community-build-dotty 17.179s

scalafmt

- important to keep consistent style for a team

- runs on CI

- can be considered a blocker to Scala 3 migration

object Main:

  def main(args: Array[String]) =
    val name: Option[String] = Some("AVeryLongString")
      name 
       match    
         case None => println("Hello <unknown>")
          case Some(x) => println(s"Hello $x")
object Main:

  def main(args: Array[String]) =
     val name: Option[String] = 
        Some("AVeryLongString")
     name 
        match    
           case None    => println("Hello <unknown>")
           case Some(x) => println(s"Hello $x")

scalafmt

version = 3.0.0-RC1
runner.dialect = scala3
indent.main = 3

scalafmt

version = 3.0.0-RC1

fileOverride {
  "glob:**/scala-3*/**" {
    runner.dialect = scala3
    indent.main = 3
  }
}

scalafmt


def hello(name: String): Unit = {
  val greeting = s"Hello $name"
  println(greeting)
}

Rewrite - drop braces


def hello(name: String): Unit =
  val greeting = s"Hello $name"
  println(greeting)

scalafmt

import scala.meta.{Main => Metals}
import scala.meta._

object Main{}

Rewrite - New syntax

import scala.meta.Main as Metals
import scala.meta.*

object Main{}

mdoc

```scala mdoc
val x = 1
val y = 2
x + y
```
```scala
val x = 1
// x: Int = 1
val y = 2
// y: Int = 2
x + y
// res0: Int = 3
```

after

Before

mdoc

```scala mdoc:crash
val y = ???
```
```scala
val y = ???
// scala.NotImplementedError: an implementation is missing
//  at scala.Predef$.$qmark$qmark$qmark(Predef.scala:288)
//  at repl.MdocSession$App$$anonfun$1.apply$mcV$sp(modifiers.md:9)
//  at repl.MdocSession$App$$anonfun$1.apply(modifiers.md:8)
//  at repl.MdocSession$App$$anonfun$1.apply(modifiers.md:8)
```

after

Before

mdoc

```scala mdoc:scalafmt
indent.callSite = 2
---
function(
argument1, // indented by 2
""
)
```
```scala formatted
function(
  argument1, // indented by 2
  ""
)
```
```scala original
function(
argument1, // indented by 2
""
)
```
<details class='config' open><summary>Config for this example:</summary><p>
```scala config
continuationIndent.callSite = 2
```
</p></details>

after

Before

mdoc

scaladoc

Typechecked scaladoc snippets

```scala sc:failing
val iAmWrong: Int = 2.3
for(i ← 1.to(10))
yield i*2
```

Borrows ideas and code from mdoc

But inside the compile

scalafix

import scala.List
import scala.collection.{immutable, mutable}
object Foo { immutable.Seq.empty[Int] }
import scala.collection.immutable
object Foo { immutable.Seq.empty[Int] }

before

after

scalafix

scalafix

import scala.meta._
import scala.collection.{mutable => mut}

object Main {}
import scala.meta.*
import scala.collection.mutable as mut

object Main {}

before

after

Your favourite language in you favourite Editor

Metals

Scala 3 support

Metals

Scala 3 Inline

Metals

inline def power(x: Double, n: Int): Double =
   if n == 0 then 1.0
   else if n == 1 then x
   else
      val y = power(x, n / 2)
      if n % 2 == 0 then y * y else y * y * x

Scala 3 auto importing implicits

Metals

Tasty!

Metals

         +-------------+    +-------------+    +-------------+
$ scalac | Hello.scala | -> | Hello.tasty | -> | Hello.class |
         +-------------+    +-------------+    +-------------+
                ^                  ^                  ^
                |                  |                  |
           Your source        TASTy file         Class file
               code           for scalac        for the JVM
                              (contains         (incomplete
                               complete         information)
                             information)

MAcros

Metals

import scala.quoted.*

inline def assert(inline expr: Boolean): Unit =
   ${ assertImpl('expr) }

def assertImpl(expr: Expr[Boolean])(using Quotes) = '{
   if !$expr then
      throw AssertionError(s"failed assertion: ${${ showExpr(expr) }}")
}

def showExpr(expr: Expr[Boolean])(using Quotes): Expr[String] =
   '{ "<some source code>" } // Better implementation later in this document

ScalaJS

ScalaJS

currently Implemented inside Scala 3 compiler

reuses most of existing code

Implemented in 3 months!

not outstanding issues

Scala native

Scala native

Windows support

multithreading

scala 3

Scala 3 will be amazing!

Question to Martin

What new tooling feature would you love to see implemented when it comes to Scala 3, but that did not exist in Scala 2?