-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Allow nested Quotes with a different owners #13652
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -525,6 +525,8 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => | |
* `Some` containing the implementation of the method. Returns `None` the method has no implementation. | ||
* Any definition directly inside the implementation should have `symbol` as owner. | ||
* | ||
* Use `Symbol.asQuotes` to create the rhs using quoted code. | ||
* | ||
* See also: `Tree.changeOwner` | ||
*/ | ||
def apply(symbol: Symbol, rhsFn: List[List[Tree]] => Option[Term]): DefDef | ||
|
@@ -602,20 +604,53 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => | |
* Returns `None` the method has no implementation. | ||
* Any definition directly inside the implementation should have `symbol` as owner. | ||
* | ||
* Use `Symbol.asQuotes` to create the rhs using quoted code. | ||
* | ||
* See also: `Tree.changeOwner` | ||
*/ | ||
def apply(symbol: Symbol, rhs: Option[Term]): ValDef | ||
def copy(original: Tree)(name: String, tpt: TypeTree, rhs: Option[Term]): ValDef | ||
def unapply(vdef: ValDef): (String, TypeTree, Option[Term]) | ||
|
||
/** Creates a block `{ val <name> = <rhs: Term>; <body(x): Term> }` */ | ||
/** Creates a block `{ val <name> = <rhs: Term>; <body(x): Term> }` | ||
* | ||
* Usage: | ||
* ``` | ||
* ValDef.let(owner, "x", rhs1) { x => | ||
* ValDef.let(x.symbol.owner, "y", rhs2) { y => | ||
* // use `x` and `y` | ||
* } | ||
* } | ||
* ``` | ||
* @syntax markdown | ||
*/ | ||
def let(owner: Symbol, name: String, rhs: Term)(body: Ref => Term): Term | ||
|
||
/** Creates a block `{ val x = <rhs: Term>; <body(x): Term> }` */ | ||
/** Creates a block `{ val x = <rhs: Term>; <body(x): Term> }` | ||
* | ||
* Usage: | ||
* ``` | ||
* ValDef.let(owner, rhs1) { x => | ||
* ValDef.let(owner, rhs2) { y => | ||
* // use `x` and `y` | ||
* } | ||
* } | ||
* ``` | ||
* @syntax markdown | ||
*/ | ||
def let(owner: Symbol, rhs: Term)(body: Ref => Term): Term = | ||
let(owner, "x", rhs)(body) | ||
|
||
/** Creates a block `{ val x1 = <terms(0): Term>; ...; val xn = <terms(n-1): Term>; <body(List(x1, ..., xn)): Term> }` */ | ||
/** Creates a block `{ val x1 = <terms(0): Term>; ...; val xn = <terms(n-1): Term>; <body(List(x1, ..., xn)): Term> }` | ||
* | ||
* Usage: | ||
* ``` | ||
* ValDef.let(owner, rhsList) { xs => | ||
* ... | ||
* } | ||
* ``` | ||
* @syntax markdown | ||
*/ | ||
def let(owner: Symbol, terms: List[Term])(body: List[Ref] => Term): Term | ||
} | ||
|
||
|
@@ -1341,6 +1376,28 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => | |
* ```scala sc:nocompile | ||
* Block((DefDef(_, _, params :: Nil, _, Some(rhsFn(meth, paramRefs)))) :: Nil, Closure(meth, _)) | ||
* ``` | ||
* | ||
* Usage: | ||
* ``` | ||
* val mtpe = MethodType(List("arg1"))(_ => List(TypeRepr.of[Int]), _ => TypeRepr.of[Int]) | ||
* Lambda(owner, mtpe, { | ||
* case (methSym, List(arg1: Term)) => | ||
* ValDef.let(methSym, f(arg1)) { ... } | ||
* } | ||
* ) | ||
* ``` | ||
* | ||
* Usage with quotes: | ||
* ``` | ||
* val mtpe = MethodType(List("arg1"))(_ => List(TypeRepr.of[Int]), _ => TypeRepr.of[Int]) | ||
* Lambda(owner, mtpe, { | ||
* case (methSym, List(arg1: Term)) => | ||
* given Quotes = methSym.asQuotes | ||
* '{ ... } | ||
* } | ||
* ) | ||
* ``` | ||
* | ||
* @param owner: owner of the generated `meth` symbol | ||
* @param tpe: Type of the definition | ||
* @param rhsFn: Function that receives the `meth` symbol and the a list of references to the `params` | ||
|
@@ -3817,6 +3874,35 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => | |
|
||
/** Case class or case object children of a sealed trait or cases of an `enum`. */ | ||
def children: List[Symbol] | ||
|
||
/** Returns a nested quote with this symbol as splice owner (`Symbol.spliceOwner`). | ||
* | ||
* Changes the owner under which the definition in a quote are created. | ||
* | ||
* Usages: | ||
* ```scala | ||
* def rhsExpr(using Quotes): Expr[Unit] = '{ val y = ???; (y, y) } | ||
* def aValDef(using Quotes)(owner: Symbol) = | ||
* val sym = Symbol.newVal(owner, "x", TypeRepr.of[Unit], Flags.EmptyFlags, Symbol.noSymbol) | ||
* val rhs = rhsExpr(using sym.asQuotes).asTerm | ||
* ValDef(sym, Some(rhs)) | ||
* ``` | ||
* | ||
* ```scala | ||
* new TreeMap: | ||
* override def transformTerm(tree: Term)(owner: Symbol): Term = | ||
* tree match | ||
* case tree: Ident => | ||
* given Quotes = owner.asQuotes | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This still looks easy too easy to miss and hard to teach to me, is there no way to automatically use the correct Quotes? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (also if we do go with this solution, it would need to be explained in details in the documentation) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
We can do this only if we use the quotes API alone. We could not find a good way to do this when reflection is involved. That is one of the reasons why we introduced the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
What about the discussion in #12309 (comment) ? /cc @odersky, @LPTK There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This adds a way to avoid the costly change of owners mentioned in #12309 (comment). It provides a way to construct the quotes under the correct owner which means that when we will only need to check that the owner is correct and not change it. Currently, if we create a quote that goes into a reflection constructor we always end up taking the slow path and need to change the owners. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
But presumably this isn't always working if #13571 emits an error? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In #13571 |
||
* // Definitions contained in the quote will be owned by `owner`. | ||
* // No need to use `changeOwner` in this case. | ||
* '{ val x = ???; x }.asTerm | ||
* ``` | ||
* @syntax markdown | ||
*/ | ||
@experimental | ||
def asQuotes: Nested | ||
|
||
end extension | ||
} | ||
|
||
|
@@ -4497,6 +4583,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => | |
* override def transformTree(tree: Tree)(owner: Symbol): Tree = ??? | ||
* } | ||
* ``` | ||
* | ||
* Use `Symbol.asQuotes` to create quotes with the correct owner within the TreeMap. | ||
* | ||
* @syntax markdown | ||
*/ | ||
trait TreeMap: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import scala.quoted.* | ||
|
||
inline def checked2[A](inline n: A): A = | ||
${ checkedImpl2[A]('{n}) } | ||
|
||
private def checkedImpl2[A](n: Expr[A])(using Quotes, Type[A]): Expr[A] = | ||
import quotes.reflect.* | ||
val tree: Term = n.asTerm | ||
val acc = new TreeMap: | ||
override def transformTerm(tree: Term)(owner: Symbol): Term = | ||
tree match | ||
case Apply(Select(x, "*"), List(y)) => | ||
given Quotes = owner.asQuotes | ||
'{ | ||
val xt = ${x.asExprOf[Long]} | ||
xt | ||
}.asTerm | ||
case _ => | ||
super.transformTerm(tree)(owner) | ||
acc.transformTerm(tree)(Symbol.spliceOwner).asExprOf[A] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
def test = { | ||
val u = 3L | ||
checked2(List(1L, 2L).map { k => | ||
u * 2L | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
import scala.quoted.* | ||
|
||
inline def checked2[A](inline n: A): A = | ||
${ checkedImpl2[A]('{n}) } | ||
|
||
private def checkedImpl2[A](n: Expr[A])(using Quotes, Type[A]): Expr[A] = | ||
import quotes.reflect.* | ||
val tree: Term = n.asTerm | ||
val acc = new TreeMap: | ||
override def transformTerm(tree: Term)(owner: Symbol): Term = | ||
tree match | ||
case Apply(Select(x, "*"), List(y)) => | ||
bindLong(x.asExprOf[Long])(using owner.asQuotes).asTerm | ||
case _ => | ||
super.transformTerm(tree)(owner) | ||
acc.transformTerm(tree)(Symbol.spliceOwner).asExprOf[A] | ||
|
||
def bindLong(expr: Expr[Long])(using Quotes): Expr[Long] = | ||
'{ | ||
val xt = $expr | ||
xt | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
def test = { | ||
val u = 3L | ||
checked2(List(1L, 2L).map { k => | ||
u * 2L | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
import scala.quoted.* | ||
|
||
inline def optPrettyPrinter[T]: Option[T] => Option[T] = | ||
${ optPrettyPrinterImpl[T] } | ||
|
||
private def optPrettyPrinterImpl[T: Type](using Quotes): Expr[Option[T] => Option[T]] = { | ||
import quotes.reflect.* | ||
|
||
val tpe = TypeRepr.of[T] | ||
|
||
val fn = Lambda( | ||
Symbol.spliceOwner, | ||
MethodType(List("macroVal"))( | ||
_ => List(tpe), | ||
_ => tpe | ||
), | ||
{ | ||
case (m, List(arg: Term)) => | ||
given Quotes = m.asQuotes | ||
ValDef.let(m, "v", arg) { v => | ||
'{ | ||
val vv = ${ v.asExprOf[T] } | ||
println("v=" + vv.toString()) | ||
vv | ||
}.asTerm | ||
} | ||
|
||
case _ => | ||
report.errorAndAbort("Fails compile") | ||
} | ||
).asExprOf[T => T] | ||
|
||
'{ (_: Option[T]).map(${ fn }) } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
def test = optPrettyPrinter(Some("foo")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think
@syntax markdown
is required in the library doc anymore, it should be the default.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will remove it in a separate PR. There is a total of 27 of these in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Follow up in #14759