-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Properly handle AnyVal
s as refinement members of Selectable
s
#16286
Conversation
G1ng3r
commented
Nov 6, 2022
- add dynamic access to value classes field
2ea376b
to
a085e6c
Compare
@prolativ Hi! Could you look into it? I'm new to compiler and not quite sure if this solution is ok or not |
tests/pos/i14340.scala
Outdated
@@ -0,0 +1,10 @@ | |||
object i14340 { |
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.
As this used to fail at runtime, we need to have a test checking if this works at runtime. Tests in tests/pos
are only checked for successful compilation. You need to move this test to tests/run/i14340.scala
and make it a runnable program (e.g. by creating a @main
method). Also for extra certainty that this actually works as expect you could add a test check file tests/run/i14340.check
containing the expected console output of the program. In this case it would be simply
1
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.
Thanks! I did that and solution is not working. Will try something else
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've found solution, but again I'm not sure about it. I think we need to wrap called members of Selectable. But it doesn't look perfect for me
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 have some doubts if Dynamic.scala
is really the proper place for the fix. I tried to play with the issue a bit and it seems specific to reflect.Selectable
rather than to Selectable
in general. For sure we need more tests here to make sure we won't break something else. Consider cases like this:
- What if we need to deal with nested value classes?
- What if what we call from a
Selectable
should desugar toapplyDynamic
rather thanselectDynamic
? - What if we provide a custom implementation of
selectDynamic
which doesn't use reflection?
An extended test case could look like this
class Foo(val value: Int) extends AnyVal
class Bar[A](val value: A) extends AnyVal
class Container1 extends reflect.Selectable
class Container2 extends Selectable:
def selectDynamic(name: String) = Bar(name)
val cont1 = new Container1:
val foo = new Foo(1)
val bar = new Bar(Foo(2))
def fooFromInt(i: Int) = new Foo(i)
val cont2 = (new Container2).asInstanceOf[Container2 { def qux: Bar[String] }]
@main def Test: Unit =
println(cont1.foo.value)
println(cont1.bar.value)
println(cont1.fooFromInt(3).value)
println(cont2.qux.value)
with expected output
1
2
3
qux
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.
Thanks a lot. I will think about another approach
AnyVal
s as refinement members of Selectable
s
d454324
to
9917bf2
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.
LGTM. Thanks for the fix!
@odersky as stated in the later comments, this doesn't seem to be fixed yet |
@G1ng3r do you plan to carry on with trying to fix this issue? |
@prolativ yep, I'm working on it. Stuck on last case, need some time |
OK, great. No pressure though. If you're having troubles with some specific issue, you can share your idea here so that we find the solution together |
9917bf2
to
fcdce7f
Compare
@prolativ I pushed my current state, and I have a problem with |
I'll have a look at it. |
Oh, you're right. Didnt think about it. I'll try to come up with something else. Thanks |
I tried to dig into that a bit and here are my thoughts: The fix should not even touch Probably the cleanest solution would be to change the implementation of There's some chance the problem could be alternatively fixed by leaving the implementation of |
- add dynamic access to value classes field
fcdce7f
to
4b8a31f
Compare
@prolativ Hi! I found the reason why my solution was not working for last case and I fixed it. |
Unfortunately this new approach doesn't work either. Just see how the test would crash if you change def selectDynamic(name: String) = Bar(name) to def selectDynamic(name: String): Any = Bar(name) (actually I would expect the return type of class Container1 extends reflect.Selectable
class Container2(values: Map[String, Any], methods: Map[String, Int => Any]) extends Selectable:
def selectDynamic(name: String) = values(name)
def applyDynamic(name: String)(arg: Int) = methods(name)(arg)
class Foo(val value: Int) extends AnyVal
class Bar[A](val value: A) extends AnyVal
object Helpers:
def foo = Foo(1)
def bar = Bar(Foo(2))
def qux1 = Bar(new Container1 { def foo = Foo(10) })
def qux2 = Bar(new Container2(Map("foo" -> Foo(20)), Map.empty).asInstanceOf[Container2 { def foo: Foo }])
def cont1 = new Container1:
def foo = Helpers.foo
val bar = Helpers.bar
def qux1 = Helpers.qux1
def qux2 = Helpers.qux2
def fooFromInt(i: Int) = Foo(i)
def cont2values = Map(
"foo" -> Helpers.foo,
"bar" -> Helpers.bar,
"qux1" -> Helpers.qux1,
"qux2" -> Helpers.qux2
)
def cont2methods = Map(
"fooFromInt" -> { (i: Int) => Foo(i) }
)
def cont2 = Container2(cont2values, cont2methods).asInstanceOf[Container2 {
def foo: Foo
def bar: Bar[Foo]
def qux1: Bar[Container1 { def foo: Foo }]
def qux2: Bar[Container2 { def foo: Foo }]
def fooFromInt(i: Int): Foo
}]
@main def Test: Unit =
val cont1 = Helpers.cont1
val cont2 = Helpers.cont2
println(cont1.foo.value)
println(cont2.foo.value)
println(cont1.bar.value.value)
println(cont2.bar.value.value)
println(cont1.qux1.value.foo.value)
println(cont2.qux1.value.foo.value)
println(cont1.qux2.value.foo.value)
println(cont2.qux2.value.foo.value)
println(cont1.fooFromInt(100).value)
println(cont2.fooFromInt(100).value) Feel free to our own test cases. |
Just for clarity: this should print
|
Thanks a lot! I'll think about another approach, and will get back :) |
@G1ng3r I think I've managed to slightly modify your approach to make this work - the main point is that special handling of @odersky do you think this workaround is acceptable? Currently I can see no other way to fix this without changing the specification of desugaring of |
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.
Seems good to me. A couple of suggestions.
@@ -214,9 +216,23 @@ trait Dynamic { | |||
def fail(reason: String): Tree = | |||
errorTree(tree, em"Structural access not allowed on method $name because it $reason") | |||
|
|||
extension (tree: Tree) | |||
/** The implementations of `selectDynamic` and `applyDynamic` in `scala.reflect.SelectDynamic` have no information about the type of a value obtained via reflection before erasure |
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.
/** The implementations of `selectDynamic` and `applyDynamic` in `scala.reflect.SelectDynamic` have no information about the type of a value obtained via reflection before erasure | |
/** The implementations of `selectDynamic` and `applyDynamic` in `scala.reflect.Selectable` both return `Any` |
@@ -214,9 +216,23 @@ trait Dynamic { | |||
def fail(reason: String): Tree = | |||
errorTree(tree, em"Structural access not allowed on method $name because it $reason") | |||
|
|||
extension (tree: Tree) | |||
/** The implementations of `selectDynamic` and `applyDynamic` in `scala.reflect.SelectDynamic` have no information about the type of a value obtained via reflection before erasure | |||
* so the can't know if the value should be wrapped in a value class constructor call or not. |
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.
* so the can't know if the value should be wrapped in a value class constructor call or not. | |
* so they can't know if the value should be wrapped in a value class constructor call or not. |
def maybeBoxingCast(tpe: Type) = | ||
val maybeBoxed = | ||
if ValueClasses.isDerivedValueClass(tpe.classSymbol) && qual.tpe <:< defn.ReflectSelectableTypeRef then | ||
val underlying = ValueClasses.valueClassUnbox(tpe.classSymbol.asClass).info.resultType.asSeenFrom(tpe, tpe.classSymbol) |
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.
val underlying = ValueClasses.valueClassUnbox(tpe.classSymbol.asClass).info.resultType.asSeenFrom(tpe, tpe.classSymbol) | |
val genericUnderlying = ValueClasses.valueClassUnbox(tpe.classSymbol.asClass) | |
val underlying = tpe.select(genericUnderlying).widen.resultType |
… non-reflective access
a032a66
to
1a6224b
Compare