Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add minimal prototype example of SIP-61-like macro annotation #19997

Merged
merged 1 commit into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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))
Loading