diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 852d7ee8b20f..5ac87b64b853 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -369,6 +369,12 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def unapply(vdef: ValDef): (String, TypeTree, Option[Term]) = (vdef.name.toString, vdef.tpt, optional(vdef.rhs)) + def let(owner: Symbol, name: String, rhs: Term, flags: Flags)(body: Ref => Term): Term = + Symbol.checkValidFlags(flags.toTermFlags, Flags.validValInLetFlags) + val vdef = tpd.SyntheticValDef(name.toTermName, rhs, flags)(using ctx.withOwner(owner)) + val ref = tpd.ref(vdef.symbol).asInstanceOf[Ref] + Block(List(vdef), body(ref)) + def let(owner: Symbol, name: String, rhs: Term)(body: Ref => Term): Term = val vdef = tpd.SyntheticValDef(name.toTermName, rhs)(using ctx.withOwner(owner)) val ref = tpd.ref(vdef.symbol).asInstanceOf[Ref] @@ -2863,7 +2869,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler def noSymbol: Symbol = dotc.core.Symbols.NoSymbol - private inline def checkValidFlags(inline flags: Flags, inline valid: Flags): Unit = + private[QuotesImpl] inline def checkValidFlags(inline flags: Flags, inline valid: Flags): Unit = xCheckMacroAssert( flags <= valid, s"Received invalid flags. Expected flags ${flags.show} to only contain a subset of ${valid.show}." @@ -3224,7 +3230,8 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler private[QuotesImpl] def validMethodFlags: Flags = Private | Protected | Override | Deferred | Final | Method | Implicit | Given | Local | AbsOverride | JavaStatic | Synthetic | Artifact // Flags that could be allowed: Synthetic | ExtensionMethod | Exported | Erased | Infix | Invisible // Keep: aligned with Quotes's `newVal` doc private[QuotesImpl] def validValFlags: Flags = Private | Protected | Override | Deferred | Final | Param | Implicit | Lazy | Mutable | Local | ParamAccessor | Module | Package | Case | CaseAccessor | Given | Enum | AbsOverride | JavaStatic | Synthetic | Artifact // Flags that could be added: Synthetic | Erased | Invisible - + // Keep: aligned with Quotes's `let` doc + private[QuotesImpl] def validValInLetFlags: Flags = Final | Implicit | Lazy | Mutable | Given | Synthetic // Keep: aligned with Quotes's `newBind` doc private[QuotesImpl] def validBindFlags: Flags = Case // Flags that could be allowed: Implicit | Given | Erased diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 009f1c28fbd9..0cf2ac6760e1 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -682,6 +682,22 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => def copy(original: Tree)(name: String, tpt: TypeTree, rhs: Option[Term]): ValDef def unapply(vdef: ValDef): (String, TypeTree, Option[Term]) + /** Creates a block `{ val = ; }` + * + * Usage: + * ``` + * ValDef.let(owner, "x", rhs1, Flags.Lazy) { x => + * ValDef.let(x.symbol.owner, "y", rhs2, Flags.Mutable) { y => + * // use `x` and `y` + * } + * } + * ``` + * + * @param flags extra flags to with which the symbol should be constructed. Can be `Final | Implicit | Lazy | Mutable | Given | Synthetic` + */ + // Keep: `flags` doc aligned with QuotesImpl's `validValInLetFlags` + def let(owner: Symbol, name: String, rhs: Term, flags: Flags)(body: Ref => Term): Term + /** Creates a block `{ val = ; }` * * Usage: diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index c57136b262dd..8878fb044603 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -19,6 +19,8 @@ object MiMaFilters { ProblemFilters.exclude[DirectMissingMethodProblem]("scala.Conversion.underlying"), ProblemFilters.exclude[MissingClassProblem]("scala.Conversion$"), + + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ValDefModule.let"), ), // Additions since last LTS @@ -124,6 +126,7 @@ object MiMaFilters { ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.methodTypeKind"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#MethodTypeMethods.isContextual"), ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ImplicitsModule.searchIgnoring"), + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ValDefModule.let"), // Change `experimental` annotation to a final class ProblemFilters.exclude[FinalClassProblem]("scala.annotation.experimental"), ), diff --git a/tests/neg-macros/i23008.check b/tests/neg-macros/i23008.check index b26ef11d201f..5193fb0ad7c4 100644 --- a/tests/neg-macros/i23008.check +++ b/tests/neg-macros/i23008.check @@ -5,8 +5,8 @@ | Exception occurred while executing macro expansion. | java.lang.IllegalArgumentException: requirement failed: value of StringConstant cannot be `null` | at scala.Predef$.require(Predef.scala:337) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2534) - | at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2533) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2540) + | at scala.quoted.runtime.impl.QuotesImpl$reflect$StringConstant$.apply(QuotesImpl.scala:2539) | at scala.quoted.ToExpr$StringToExpr.apply(ToExpr.scala:80) | at scala.quoted.ToExpr$StringToExpr.apply(ToExpr.scala:78) | at scala.quoted.Expr$.apply(Expr.scala:70) diff --git a/tests/run-macros/ValDef-let-flags.check b/tests/run-macros/ValDef-let-flags.check new file mode 100644 index 000000000000..9898cc629e93 --- /dev/null +++ b/tests/run-macros/ValDef-let-flags.check @@ -0,0 +1 @@ +x: 1, y: string \ No newline at end of file diff --git a/tests/run-macros/ValDef-let-flags/Macro_1.scala b/tests/run-macros/ValDef-let-flags/Macro_1.scala new file mode 100644 index 000000000000..fc2c5f77628b --- /dev/null +++ b/tests/run-macros/ValDef-let-flags/Macro_1.scala @@ -0,0 +1,15 @@ +import scala.quoted._ + +object Macro: + inline def makeBlock(): Unit = ${makeBlockImpl} + def makeBlockImpl(using Quotes): Expr[Unit] = { + import quotes.reflect._ + ValDef.let(Symbol.spliceOwner, "x", '{0}.asTerm, Flags.Mutable) { x => + ValDef.let(Symbol.spliceOwner, "y", '{"string"}.asTerm, Flags.Lazy) { y => + '{ + ${Assign(x, '{1}.asTerm).asExpr} + println("x: " + ${x.asExprOf[Int]} + ", y: " + ${y.asExprOf[String]}) + }.asTerm + } + }.asExprOf[Unit] + } diff --git a/tests/run-macros/ValDef-let-flags/Test_2.scala b/tests/run-macros/ValDef-let-flags/Test_2.scala new file mode 100644 index 000000000000..00b0a955829b --- /dev/null +++ b/tests/run-macros/ValDef-let-flags/Test_2.scala @@ -0,0 +1 @@ +@main def Test = Macro.makeBlock()