Skip to content

Add missing version of ValDef.let which also accepts flags #23388

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
11 changes: 9 additions & 2 deletions compiler/src/scala/quoted/runtime/impl/QuotesImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's also check if the flags are valid, as suggested in the doc:

Suggested change
val vdef = tpd.SyntheticValDef(name.toTermName, rhs, flags)(using ctx.withOwner(owner))
checkValidFlags(flags.toTermFlags, Flags.validValFlags)
val vdef = tpd.SyntheticValDef(name.toTermName, rhs, flags)(using ctx.withOwner(owner))

Of course, as with my other comment, the flags should ideally be different here than in Flags.validValFlags

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accepted suggestion as-is. Very open to limiting allowed flags, if I knew what that allowed set was.

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]
Expand Down Expand Up @@ -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}."
Expand Down Expand Up @@ -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

Expand Down
16 changes: 16 additions & 0 deletions library/src/scala/quoted/Quotes.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 <name> = <rhs: Term>; <body(x): Term> }`
*
* 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 <name> = <rhs: Term>; <body(x): Term> }`
*
* Usage:
Expand Down
3 changes: 3 additions & 0 deletions project/MiMaFilters.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"),
),
Expand Down
4 changes: 2 additions & 2 deletions tests/neg-macros/i23008.check
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions tests/run-macros/ValDef-let-flags.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
x: 1, y: string
15 changes: 15 additions & 0 deletions tests/run-macros/ValDef-let-flags/Macro_1.scala
Original file line number Diff line number Diff line change
@@ -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]
}
1 change: 1 addition & 0 deletions tests/run-macros/ValDef-let-flags/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@main def Test = Macro.makeBlock()
Loading