Metaprogramming

transparent inline lets the result type be refined per call site.

32.1 inline

transparent inline lets the result type be refined per call site.

inline val Debug = false
inline def log(inline msg: String): Unit =
  inline if Debug then println(msg)      // removed entirely at compile time when Debug = false

inline def power(x: Double, inline n: Int): Double =
  inline if n == 0 then 1.0
  else if n % 2 == 1 then x * power(x, n - 1)
  else { val y = power(x, n / 2); y * y }   // fully unrolled at compile time

32.2 compiletime utilities

import scala.compiletime.*

inline def typeName[T]: String = inline erasedValue[T] match
  case _: Int => "Int"
  case _: String => "String"
  case _ => "Other"

inline def requirePositive(inline n: Int): Int =
  inline if n <= 0 then error("must be positive") else n

summonInline[Ordering[Int]], constValue[3], codeOf(1 + 2)

32.3 Macros with quotes & splices

'{ ... } quotes code into an Expr[T]; ${ ... } splices an Expr into code.

quotes.reflect gives full access to the typed AST (TASTy reflection).

Scala 3 macros are not compatible with Scala 2 macros (scala.reflect.macros).

// Macros.scala (must be compiled before use — separate file/module)
import scala.quoted.*

inline def debug(inline expr: Any): Unit = ${ debugImpl('expr) }

def debugImpl(expr: Expr[Any])(using Quotes): Expr[Unit] =
  val code = expr.show
  '{ println(${ Expr(code) } + " = " + $expr) }

// Usage
val x = 2
debug(x * 21)      // prints: x.*(21) = 42

32.4 Mirrors & type class derivation

Libraries: magnolia (derivation helper), shapeless 3, kittens.

import scala.deriving.Mirror
import scala.compiletime.{erasedValue, summonInline, constValueTuple}

trait Show[A]:
  def show(a: A): String

object Show:
  given Show[Int] = _.toString
  given Show[String] = s => s"\"$s\""

  inline def derived[A](using m: Mirror.ProductOf[A]): Show[A] =
    val labels = constValueTuple[m.MirroredElemLabels].toList.map(_.toString)
    val instances = summonAll[m.MirroredElemTypes]
    (a: A) =>
      val values = a.asInstanceOf[Product].productIterator.toList
      val fields = labels.zip(values.zip(instances)).map { case (l, (v, s)) =>
        s"$l = ${s.asInstanceOf[Show[Any]].show(v)}" }
      fields.mkString(s"${constValue[m.MirroredLabel]}(", ", ", ")")

  inline def summonAll[T <: Tuple]: List[Show[?]] = inline erasedValue[T] match
    case _: EmptyTuple => Nil
    case _: (t *: ts) => summonInline[Show[t]] :: summonAll[ts]

import scala.compiletime.constValue
case class Person(name: String, age: Int) derives Show
summon[Show[Person]].show(Person("Ann", 30))   // Person(name = "Ann", age = 30)

32.5 Annotations

@main, @tailrec, @deprecated, @inline, @specialized (Scala 2), @targetName, @throws, @transient, @volatile, @unchecked, @nowarn, @experimental, @static, @publicInBinary.

32.6 Runtime reflection

Scala 3 has no runtime scala.reflect.runtime; use Java reflection or compile-time techniques. ClassTag and TypeTest remain available.