-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Macro extending selectable: very strange select behavior when accessing member value #20100
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
Comments
I would like to add the following: if I add several member values to the extending class |
Huh, so if I understand correctly, it looks like after generating and instantiating a Selectable class in a macro method:
|
In regards to your first point, it seems like it. Note that I may be doing something wrong in the code above or it may be an interaction of some other kind. However, note that If I remove the base class member value test is not a member of examples.instantiate.MyMacro5Vals.SelectableBase{
val field: (3 : Int); val field1: ("f2" : String)}bloop(8) As for the second point, honestly I cannot say why I used that flag. Now that you mention it, it does not seem to make sense here (not required). Did you use EDIT: was able to reproduce the same run-time error. |
I tried to replicate the initial code, but without the macro base class construction. Here is what I came up with: object Hidden6:
import scala.quoted.*
trait SelectableBase extends Selectable:
val test = "test"
transparent inline def selectDynamic(name: String): Any =
${selectDynamicImpl('this, 'name)}
def applyDynamic(name: String)(args: Any*): Any = ???
def selectDynamicImpl[A:Type](inner: Expr[A], name: Expr[String])(using quotes: Quotes): Expr[Any] =
import quotes.reflect.*
Select.unique(inner.asTerm, name.valueOrAbort).asExpr
???
class RecH() extends SelectableBase:
// Accessed directly, no select dynamic
val name: String = "C"
val age: Int = 2
val sex: Char = 'M'
def makeRecH() =
new RecH() If I use it as follows: val person12 = Hidden6.makeRecH()
println(person12.test)
println(person12.name)
println(person12.sex)
It compiles and runs. So the direct access is correct (even for the base So the the issue here is why |
Ah, so it looks like my analysis before was incorrect - I did not know about how the structural type was automatically created for anonymous classes. I see that selectDynamic is going to be only called for the refinement members (
So this means the only things to fix here is the macro unnecessarily running for inline vals in anonymous classes/refinements, like in the original example, or in the last one if we do something like |
Seems to make sense what you say. I am going with the first option - remove the From my side, this issue can be closed. |
After some consideration, I decided not to make any changes in the compiler relating to this. Let me explain (this is mostly for the compiler mainteiners and anyone who will choose to revisit this in the future). Let’s start with a minimization: object Test:
def main(args: Array[String]): Unit =
val rec = makeSelectable()
rec.number Macro.scala import scala.quoted.*
trait SelectableBase extends Selectable:
val test: String = "a"
transparent inline def selectDynamic(name: String): Any =
${selectDynamicImpl('this, 'name)}
def selectDynamicImpl[A:Type](inner: Expr[A], name: Expr[String])(using quotes: Quotes): Expr[Any] =
import quotes.reflect.*
println("MACRO CALLED")
Select.unique(inner.asTerm, "test").asExpr
def makeSelectable() = // : SelectableBase{val age: 0}
new SelectableBase(){inline val number = 0} The problem here is the fact that calling rec.number will call the selectDynamic macro, however the value returned by that macro will be replaced by the literal type later in a compilation, which can feel a little unexpected.
Later in the FirstTransform phase, casts to literal types are optimized into the contents of the type, so here it would change into:
So even though the macro was executed in an earlier phase, the results were discarded. I found this a bit confusing so I tried to „fix” that, thinking of 3 ways to do so:
All of the above behaviors are also confusing, if not even more… This connected with the fact that the users can write:
and expect to obtain |
Compiler version
3.4.2-RC1
Minimized code
Macro file:
Using the macro in another file:
Output
Expectation
The
selectDynamicImpl
used this:so I expect the value "test" of
trait SelectableBase
. Instead I get3
that is the value of the newly defined an instantiatedStruct.field
.What is more troubling is that if I change the code to:
I get:
Using any member that does not exist in the base class or the new
Struct
produces the expected compilation error:The text was updated successfully, but these errors were encountered: