From 2aa9079256184b16d1b8593e8558795e83502a3e Mon Sep 17 00:00:00 2001 From: Kalin-Rudnicki Date: Tue, 17 Jun 2025 16:49:21 -0600 Subject: [PATCH 1/2] Add missing version of `ValDef.let` which accepts flags --- .../scala/quoted/runtime/impl/QuotesImpl.scala | 8 +++++++- library/src/scala/quoted/Quotes.scala | 16 ++++++++++++++++ project/MiMaFilters.scala | 3 +++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index 852d7ee8b20f..e8ac90a37f69 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.validValFlags) + 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}." diff --git a/library/src/scala/quoted/Quotes.scala b/library/src/scala/quoted/Quotes.scala index 009f1c28fbd9..4336dea37681 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 `Private | Protected | Override | Deferred | Final | Param | Implicit | Lazy | Mutable | Local | ParamAccessor | Module | Package | Case | CaseAccessor | Given | Enum | JavaStatic | Synthetic | Artifact` + */ + // Keep: `flags` doc aligned with QuotesImpl's `validValFlags` + 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..fb9226997ee8 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -19,6 +19,9 @@ object MiMaFilters { ProblemFilters.exclude[DirectMissingMethodProblem]("scala.Conversion.underlying"), ProblemFilters.exclude[MissingClassProblem]("scala.Conversion$"), + + ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ValDefModule.let"), + ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ValDefModule.let"), ), // Additions since last LTS From 37ec30c8ae0b6fbb98d8257c998d4ebda882238f Mon Sep 17 00:00:00 2001 From: Jan Chyb Date: Thu, 24 Jul 2025 16:09:17 +0200 Subject: [PATCH 2/2] Allow only select flags, add test and fix CI issues --- .../scala/quoted/runtime/impl/QuotesImpl.scala | 5 +++-- library/src/scala/quoted/Quotes.scala | 4 ++-- project/MiMaFilters.scala | 2 +- tests/neg-macros/i23008.check | 4 ++-- tests/run-macros/ValDef-let-flags.check | 1 + tests/run-macros/ValDef-let-flags/Macro_1.scala | 15 +++++++++++++++ tests/run-macros/ValDef-let-flags/Test_2.scala | 1 + 7 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 tests/run-macros/ValDef-let-flags.check create mode 100644 tests/run-macros/ValDef-let-flags/Macro_1.scala create mode 100644 tests/run-macros/ValDef-let-flags/Test_2.scala diff --git a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala index e8ac90a37f69..5ac87b64b853 100644 --- a/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala +++ b/compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala @@ -370,7 +370,7 @@ class QuotesImpl private (using val ctx: Context) extends Quotes, QuoteUnpickler (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.validValFlags) + 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)) @@ -3230,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 4336dea37681..0cf2ac6760e1 100644 --- a/library/src/scala/quoted/Quotes.scala +++ b/library/src/scala/quoted/Quotes.scala @@ -693,9 +693,9 @@ trait Quotes { self: runtime.QuoteUnpickler & runtime.QuoteMatching => * } * ``` * - * @param flags extra flags to with which the symbol should be constructed. Can be `Private | Protected | Override | Deferred | Final | Param | Implicit | Lazy | Mutable | Local | ParamAccessor | Module | Package | Case | CaseAccessor | Given | Enum | JavaStatic | Synthetic | Artifact` + * @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 `validValFlags` + // 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 = ; }` diff --git a/project/MiMaFilters.scala b/project/MiMaFilters.scala index fb9226997ee8..8878fb044603 100644 --- a/project/MiMaFilters.scala +++ b/project/MiMaFilters.scala @@ -20,7 +20,6 @@ object MiMaFilters { ProblemFilters.exclude[DirectMissingMethodProblem]("scala.Conversion.underlying"), ProblemFilters.exclude[MissingClassProblem]("scala.Conversion$"), - ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ValDefModule.let"), ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ValDefModule.let"), ), @@ -127,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()