diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index d6e13fbbd168..4d6d65cde031 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -305,6 +305,11 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler val vdefs = terms.map(term => tpd.SyntheticValDef("x".toTermName, term)(using ctx1)) val refs = vdefs.map(vdef => tpd.ref(vdef.symbol).asInstanceOf[Ref]) Block(vdefs, body(refs)) + + def let(owner: Symbol, symbol: Symbol, rhs: Term)(body: Ref => Term): Term = + val vdef = apply(symbol, Some(rhs)).changeOwner(owner) + val ref = tpd.ref(vdef.symbol).asInstanceOf[Ref] + Block(List(vdef), body(ref)) end ValDef given ValDefMethods: ValDefMethods with diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 397f9d3b6aae..f1ec7cb5d702 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -607,6 +607,41 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => /** Creates a block `{ val x1 = ; ...; val xn = ; }` */ def let(owner: Symbol, terms: List[Term])(body: List[Ref] => Term): Term + + /** + * Creates a block `{ val x = ; }` + * with the given symbol for the `val` (allowing to specify `Flags`). + * + * Usage: + * ```scala sc:nocompile + * val tpe = TypeRepr.of[String] + * + * val valSym = Symbol.newVal( + * Symbol.spliceOwner, + * "myValName", + * tpe, + * Flags.Lazy, + * Symbol.noSymbol + * ) + * + * ValDef.let(Symbol.spliceOwner, valSym, Expr("foo")) { v => + * '{ println(v) }.asTerm + * } + * ``` + * + * In this way, it's possible to create an `Ref` to the `val` + * before its definition (required for recursive/lazy definitions). + * + * ```scala sc:nocompile + * // After `tpe` and `valSym` but before `let` call + * val earlyRef = Typed(Ref(valSym), Inferred(tpe)) + * ``` + * + * @param symbol the `val` symbol + * @see `Symbol.newVal` + */ + @experimental + def let(owner: Symbol, symbol: Symbol, rhs: Term)(body: Ref => Term): Term } /** Makes extension methods on `ValDef` available without any imports */ diff --git a/tests/run-macros/i13929.check b/tests/run-macros/i13929.check new file mode 100644 index 000000000000..31ee7dfeced4 --- /dev/null +++ b/tests/run-macros/i13929.check @@ -0,0 +1,2 @@ +early=i13929 +vref=i13929 \ No newline at end of file diff --git a/tests/run-macros/i13929/Macro_1.scala b/tests/run-macros/i13929/Macro_1.scala new file mode 100644 index 000000000000..4a103c291eba --- /dev/null +++ b/tests/run-macros/i13929/Macro_1.scala @@ -0,0 +1,26 @@ +import scala.quoted.* + +inline def testSymLet[T](f: T) = ${ testSymLetImpl[T]('f) } + +def testSymLetImpl[T: Type](f: Expr[T])(using Quotes): Expr[Unit] = { + import quotes.reflect.* + + val tpe = TypeRepr.of[T] + + val valSym = Symbol.newVal( + Symbol.spliceOwner, + "myVal", + tpe, + Flags.Lazy, + Symbol.noSymbol + ) + + val earlyRef = Typed(Ref(valSym), Inferred(tpe)) + + ValDef.let(Symbol.spliceOwner, valSym, f) { vref => + '{ + println("early=" + ${earlyRef.asExpr}.toString()) + println("vref=" + ${vref.asExpr}.toString()) + }.asTerm + }.asExprOf[Unit] +} diff --git a/tests/run-macros/i13929/Test_2.scala b/tests/run-macros/i13929/Test_2.scala new file mode 100644 index 000000000000..cbea2e40fb60 --- /dev/null +++ b/tests/run-macros/i13929/Test_2.scala @@ -0,0 +1 @@ +@main def Test = testSymLet("i13929")