Skip to content

Commit

Permalink
Add minimal prototype example of SIP-61-like macro annotation (#19997)
Browse files Browse the repository at this point in the history
  • Loading branch information
hamzaremmal authored Mar 21, 2024
2 parents 0ea0eba + eef3808 commit a36849f
Show file tree
Hide file tree
Showing 3 changed files with 74 additions and 0 deletions.
2 changes: 2 additions & 0 deletions tests/run-macros/annot-unroll.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
a3false
a3true
56 changes: 56 additions & 0 deletions tests/run-macros/annot-unrollLast/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//> using options -experimental -Yno-experimental

package example

import scala.annotation.{experimental, MacroAnnotation, StaticAnnotation}
import scala.quoted._
import scala.collection.mutable.Map
import scala.compiletime.ops.double

// TODO make unrollLast the macro annotation and remove unrollHelper
class unrollLast extends StaticAnnotation

@experimental
class unrollHelper extends MacroAnnotation {
def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
import quotes.reflect._
tree match
case tree: DefDef => transformDefDef(tree)
case _ => report.throwError("unrollHelper can only be applied to a method definition", tree.pos)

private def transformDefDef(using Quotes)(ddef: quotes.reflect.DefDef): List[quotes.reflect.Definition] =
import quotes.reflect._
val unrollLastSym = Symbol.requiredClass("example.unrollLast")
ddef.paramss match
case Nil =>
report.throwError("unrollHelper must have an @unrollLast parameter", ddef.pos)
case _ :: _ :: _ =>
report.throwError("unrollHelper does not yet support multiple parameter lists", ddef.pos)
case TermParamClause(params) :: Nil =>
if params.isEmpty then report.throwError("unrollHelper must have an @unrollLast parameter", ddef.pos)
else if params.init.exists(_.symbol.hasAnnotation(unrollLastSym)) || !params.last.symbol.hasAnnotation(unrollLastSym) then
report.throwError("@unrollLast must be on the last parameter", ddef.pos)
List(ddef, makeTelescopedDefDefWithoutLastArgument(ddef.symbol))
case _ =>
report.throwError("unrollHelper does not yet support type parameters", ddef.pos)

private def makeTelescopedDefDefWithoutLastArgument(using Quotes)(defSym: quotes.reflect.Symbol): quotes.reflect.DefDef =
import quotes.reflect._
def ddef1Rhs(argss: List[List[Tree]]): Some[Term] =
val defaultArg = defaultGetter(defSym, argss.size + 2) // +1 for 1-based and +1 for the argument that was dropped
val args1 = argss.head.asInstanceOf[List[Term]] :+ defaultArg
Some(Ref(defSym).appliedToArgs(args1))
val sym1 = makeTelescopedSymbolWithoutLastArgument(defSym)
DefDef(sym1, ddef1Rhs)

private def makeTelescopedSymbolWithoutLastArgument(using Quotes)(defSym: quotes.reflect.Symbol): quotes.reflect.Symbol =
import quotes.reflect._
val info1 = defSym.info match
case info: MethodType => MethodType(info.paramNames.init)(_ => info.paramTypes.init, _ => info.resType)
Symbol.newMethod(defSym.owner, defSym.name, info1, Flags.EmptyFlags, Symbol.noSymbol)

private def defaultGetter(using Quotes)(sym: quotes.reflect.Symbol, idx: Int): quotes.reflect.Term =
import quotes.reflect._
val getterSym = sym.owner.methodMember(sym.name + "$default$" + idx).head
Ref(getterSym)
}
16 changes: 16 additions & 0 deletions tests/run-macros/annot-unrollLast/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//> using options -experimental -Yno-experimental

import example.{unrollHelper, unrollLast}

class Bar:
@unrollHelper
def foo(s: String, n: Int = 1, @unrollLast b: Boolean = true): String = s + n + b
// generates: def foo(s: String, n: Int): String = foo(s, n, this.foo$default$3)

@main def Test =
import scala.reflect.Selectable.reflectiveSelectable
val bar = new Bar
val oldBar = bar.asInstanceOf[Any { def foo(s : String, n: Int): String }]

println(bar.foo("a", 3, false))
println(oldBar.foo("b", 4))

0 comments on commit a36849f

Please sign in to comment.