Scala 2 vs Scala 3 macros

Mateusz Kubuszok

About me

Agenda

  • What is a macro method?

  • Expressions and types

  • Symbols

  • Examples showing similarities and differences when solving the same small problems

  • I will focus on examples that I saw while maintaining my libraries

  • I will NOT focus on macro annotations

What is macro method?

We can intuitively think that macro method is a code generator pretending to be some object’s method.

Let’s see an example of a very simple macro

  1. Calling the impl method is the only thing we are allowed to do.

  2. Expr[A] is the AST of code that represents the value of type A.

  3. Why Scala 2 call it blackbox will be explained later.

  4. Scala 3 has "global" expressions while Scala 2 use path-dependent types for them.

Can the AST generation be in a different place than macro method?

  1. Impl doesn’t have to be in the same definition as unquoting - it doesn’t even have to be in the same package!

  2. Impl has to be defined in a preceeding compilation unit to call site (macro referring to it can be in the same as call site).

Let’s call some method in the macro

Let’s try printing some parameters

Scala 2

Scala 3

(c: scala.reflect.macros.blackbox.Context)

(using quotes: scala.quoted.Quotes)

c.WeakTypeTag[A] (or c.TypeTag[A])

scala.quoted.Type[A]

c.Type

quotes.reflect.TypeRepr

c.Expr[A]

scala.quoted.Expr[A]

c.universe.Tree

quotes.reflect.Tree

c.prefix

no counterpart

Scala 2

Scala 3

weakTypeOf[A]: c.Type

TypeRepr.of[A]: TypeRepr

c.WeakTypeTag[A](tpe: c.Type)

(tpe: TypeRepr).asType.asInstanceOf[Type[A]]

expr.tree

expr.asTerm

c.Expr[A](tree)

tree.asExprOf[A]

Scala 2

Scala 3

show(expr) or showCode(expr)

expr.asTerm.show or expr.asTerm.show(using Printer.TreeCode)

no counterpart

expr.asTerm.show(using Printer.TreeAnsiCode)

showRaw(expr)

expr.asTerm.show(using Printer.TreeStrucrture)

weakTypeOf[A].toString

TypeRepr.of[A].show or TypeRepr.of[A].show(using Printer.TypeReprCode)

no counterpart

TypeRepr.of[A].show(using Printer.TypeReprAnsiCode)

showRaw(weakTypeOf[A])

TypeRepr.of[A].show(using Printer.TypeReprStructure)

Scala 2

Scala 3

c.enclosingPosition

Position.ofMacroExpansion

c.echo(pos, msg) or c.echo(msg)

report.info(msg, pos) or report.info(msg) or report.info(msg, expr)

c.warn(pos, msg)

report.warning(msg, pos) or report.warning(msg) or report.warning(msg, expr)

c.error(pos, msg)

report.error(msg, pos) or report.error(msg) or report.error(msg, expr)

c.abort(pos, msg)

report.errorAndAbort(msg, pos) or report.errorAndAbort(msg) or report.errorAndAbort(msg, expr)

Analyzing types

Symbol - a reference to definition (type, class, val, var, method, parameter, binding…​).

Scala 2

Scala 3

(tpe: c.Type).typeSymbol

(repr: TypeRepr).typeSymbol

sym.isType / sym.isClass / sym.isModule / sym.isTerm

sym.isType / sym.isClassDef / --- / sym.isTerm

sym.asType, sym.asClass, sym.asModule, sym.asTerm

only 1 kind of Symbol

sym.asClass.primaryConstructor

sym.primaryConstructor

NoSymbol

Symbol.noSymbol

(tpe: c.Type).decls

sym.declaredFields / sym.declaredMethods

(tpe: c.Type).members

sym.fieldMembers / sym.methodMembers

typeParams (Scala 2)

paramLists (Scala 2)

paramSymss (Scala 3)

def method: Unit

List()

List()

List()

def method(): Unit

List()

List(List())

List(List())

def method(a: Int, b: String): Unit

List()

List(List(value a, value b))

List(List(val a, val b))

def method(a: Int)(b:String):Unit

List()

List(List(value a), List(value b))

List(List(val a), List(val b))

def method[A]: Unit

List(type A)

List()

List(List(type A))

def method[A](a: A): Unit

List(type A)

List(value a)

List(List(type A), List(val b))

extension [A](a: A) def method[b](b: B): Unit

List(List(type A), List(val a), List(type B), List(val b))

Building expressions

Example:

  • take a type of a case class/sealed trait

  • try to create List with a value of this type

  • for case class create a value if all params has default value

  • for sealed, create all children that can be created (case objects lub case classes like above)

Skeletons in the closet

Other differences

Scala 2

Scala 3

def method = macro methodImpl

def methodImpl(c.blackbox.Context): c.Expr[…​]

inline def method = ${ methodImpla }

def methodImpl(using Quotes): Expr[…​]

def method = macro methodImpl

def methodImpl(c.whitebox.Context): c.Expr[…​]

transparent inline def method = ${ methodImpla }

def methodImpl(using Quotes): Expr[…​]

"macro bundle"

no counterpart

Summary

  • basic concepts - typed and untyped expressions and types, AST, Symbols - are the same

  • Scala 2 APIs have more utilities, Scala 3 had more consistent utilities

  • both implementations have enough features to build upon them

  • both implementations have rather basic documentation

  • examples and slides available on my GitHub (GitHub.com/MateuszKubuszok)

Questions?

Thank You!