-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
base: main
Are you sure you want to change the base?
Add missing version of ValDef.let
which also accepts flags
#23388
Conversation
Not sure what the correct way to add this is so that it doesnt fail that bincompat test. Could use some advisement on that |
3cc71be
to
acd09a3
Compare
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.
Hi @Kalin-Rudnicki. Thank you for submitting the PR. I have some comments about the supported flags (otherwise the PR looks good).
To resolve the mima issues, you have to add a filter in MimaFilters.scala (the exact line to add should be suggested in the CI logs somewhere). This is not a problem, because we can only merge changes like this when we release new minor versions.
@@ -369,6 +369,11 @@ 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 = | |||
val vdef = tpd.SyntheticValDef(name.toTermName, rhs, flags)(using ctx.withOwner(owner)) |
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.
Let's also check if the flags are valid, as suggested in the doc:
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
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.
Accepted suggestion as-is. Very open to limiting allowed flags, if I knew what that allowed set was.
* } | ||
* ``` | ||
* | ||
* @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` |
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.
Since we know that this new val is going to be defined in a Block (as opposed to class, object, etc.), we know that less flags/modifiers are going to be allowed there, so ideally we would limit this list here (e.g. access modifiers like Private
, or Protected
do not make sense to have here).
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.
Is there a defined list where the allowed subset is defined? The only ones that I can think of, or at least that I was trying to use, were Lazy
and Mutable
.
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.
To your point, if only a valid subset is allowed, it would probably make sense to check for what that subset is.
Also, just putting the idea out there, it would be really nice if quoted code let you splice in a Definition
.
Feels kinda sad to have this:
{
lazy val instance_a: oxygen.core.typeclass.Show[scala.Int] = oxygen.core.typeclass.Show.int
lazy val instance_b: oxygen.core.typeclass.Show[scala.Option[java.lang.String]] = oxygen.core.typeclass.Show.option[java.lang.String](oxygen.core.typeclass.Show.string)
final class $anon() extends oxygen.core.typeclass.Show.PreferBuffer[oxygen.meta.NewDeriveShowSpec.CaseClass1] {
override def writeTo(builder: scala.collection.mutable.StringBuilder, value: oxygen.meta.NewDeriveShowSpec.CaseClass1): scala.Unit = {
builder.append("CaseClass1(a = ")
instance_a.writeTo(builder, value.a)
builder.append(", b = ")
instance_b.writeTo(builder, value.b)
builder.append(")")
()
}
}
(new $anon(): oxygen.core.typeclass.Show.PreferBuffer[oxygen.meta.NewDeriveShowSpec.CaseClass1])
}
This would be nicer if it wasnt so difficult:
{
final class $anon() extends oxygen.core.typeclass.Show.PreferBuffer[oxygen.meta.NewDeriveShowSpec.CaseClass1] {
private lazy val instance_a: oxygen.core.typeclass.Show[scala.Int] = oxygen.core.typeclass.Show.int
private lazy val instance_b: oxygen.core.typeclass.Show[scala.Option[java.lang.String]] = oxygen.core.typeclass.Show.option[java.lang.String](oxygen.core.typeclass.Show.string)
override def writeTo(builder: scala.collection.mutable.StringBuilder, value: oxygen.meta.NewDeriveShowSpec.CaseClass1): scala.Unit = {
builder.append("CaseClass1(a = ")
instance_a.writeTo(builder, value.a)
builder.append(", b = ")
instance_b.writeTo(builder, value.b)
builder.append(")")
()
}
}
(new $anon(): oxygen.core.typeclass.Show.PreferBuffer[oxygen.meta.NewDeriveShowSpec.CaseClass1])
}
Realizing there is probably some weirdness there to get things to line up, though, would probably need a new syntax...
final case class Definitions(exprs: Seq[Ref])
private def definitions: Definitions =
'%{
private val myVal1: String = "value-1"
private def myVal2(input: String): String = s"value-2:$input"
}
override def derive: Expr[Show[A]] =
'{
new PreferBuffer[A] {
$%{ definitions as blah }
override def writeTo(builder: mutable.StringBuilder, value: A): Unit = {
builder.append(${ blah.exprs(0).asExprOf[String] })
builder.append('\n')
builder.append(${ blah.exprs(1).appliedToArg(Expr("hi").toTerm).asExprOf[String] })
}
}
}
This is not the right place to be suggesting this... 😅
736dc18
to
cd8db82
Compare
cd8db82
to
2aa9079
Compare
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.
Sorry for adding more work, let me know if you would want me to take this over. Also, previously I somehow didn't notice that there aren't any tests here (sorry about that...). Ideally we would have one. Something simple should be enough, like:
tests/run-macros/ValDef-let-flags/Macro_1.scala
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.Lazy) { x =>
ValDef.let(Symbol.spliceOwner, "y", '{"string"}.asTerm, Flags.Mutable) { y =>
'{
${Assign(x, '{1}.asTerm).asExpr}
println("x: " + ${x.asExprOf[Int]} + " " + ${y.asExprOf[String]})
}.asTerm
}
}.asExprOf[Unit]
}
tests/run-macros/ValDef-let-flags/Test_2.scala
@main def Test = Macro.makeBlock()
tests/run-macros/ValDef-let-flags.check
x: 1, y: string
|
||
ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ValDefModule.let"), | ||
ProblemFilters.exclude[DirectMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ValDefModule.let"), |
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.
Those won't do much here. Instead, put ProblemFilters.exclude[ReversedMissingMethodProblem]("scala.quoted.Quotes#reflectModule#ValDefModule.let")
in BackwardsBreakingChanges
, somewhere under the // ReversedMissingMethodProblem
s are acceptable. See comment in Breaking changes since last LTS
` comment. Sorry for not being clear before
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 can take another stab at it, and if I cant get it, Im happy with you getting it over the finish line.
Im not sure why other tests seem to be failing though... Are there some sort of bootstrap steps that I have not executed?
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.
Ah, it seems we recently added a test that depends on line positions in QuotesImpl.scala... Sorry about that. You can run sbt scala3-bootstrapped/testCompilation i23008
and then mv tests/neg-macros/i23008.check.out tests/neg-macros/i23008.check
and commit the newly generated .check file. This should fix the issue.
* } | ||
* ``` | ||
* | ||
* @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` |
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.
In my opinion, after checking what can work in a Block
and what makes sense there, the flags consist of Final | Implicit | Lazy | Mutable | Given | Synthetic
. To make things easier, something like private[QuotesImpl] def validValInLetFlags
with the flags above should be defined in FlagsModule
in QuotesImpl.scala
, like it was before with validValFlags
(then we would use it instead of validValFlags
in the comment below and in the QuotesImpl.scala implementation).
Seems like a pretty straight forward one.
Symbol.newVal
which accepts flagsValDef.let
are just passingEmptyFlags
by way of default arg