Is there an explicit reason why the language/compiler shouldn’t/can’t support selectDynamic
via an extension method instead of directly extending scala.Dynamic
/ scala.Selectable
?
It could, but we chose the “it shouldn’t” interpretation. Mostly because that’s very dynamic already, it seems dangerous to be dynamic and named-based-only.
So either your object is a scala.Selectable
and has selectDynamic
, or it can be implicitly converted to a scala.Selectable
that has a selectDynamic
. But there needs to be the semantics of scala.Selectable
somewhere in the process.
For scala.Dynamic
, it’s even more dynamic, and so only being an actual subtype of scala.Dynamic
qualifies.
I found myself in a situation where I have a Foo[T]
typeclass that only needs to support selectDynamic
for a specific T
. It is quite annoying to pollute the entire Foo
with selectDynamic
, to support this.
If it is in a scala.Selectable
context, you can do this:
implicit final class FooIntOps(self: Foo[Int]) extends Selectable {
def selectDynamic(name: String): R = ...
}
and that will work.
If you’re in a scala.Dynamic
context, this becomes really tricky. But I guess you could do
class Foo[T] extends scala.Dynamic {
def selectDynamic(name: String)(using T =:= Int): R = ...
}
I am aware of this, but the problem is that this:
val fooString : Foo[String] = ???
fooString.junk //error about missing given/implicit `T =:= Int`
creates an error that is different from the normal error.
OK, I understand the reasoning behind not allowing selectDynamic
in an extension.
Is there a trick to abort with the original error that the compiler throws if selectDynamic
didn’t exist (for all types but the specific I want)?
Not that I know of. But perhaps someone can come up with something.
It looks doable with a whitebox implicit conversion macro, which could silently fail and be ignored if the type is not right. See also: https://github.com/lampepfl/dotty/issues/10213
Can you please provide an example. I don’t understand how implicit conversion help here.
I’ve come up with this workaround to reroute the selectDynamic
to the underlying value if T
is scala.Dynamic
.
trait UnselectableFoo[T] {
value : T
}
trait Foo[T] extends UnselectableFoo[T] with scala.Dynamic {
def selectDynamic(name : String) : Any = macro Foo.selectDynamicMacro[T]
}
object Foo {
def selectDynamicMacro[T <: c.WeakTypeTag](
c : whitebox.Context
)(name : c.Tree) : c.Tree = {
import c.universe._
val tpe = weakTypeOf[T]
val Literal(Constant(nameStr : String)) = name
if (tpe <:< typeOf[scala.Dynamic]) q"${c.prefix.tree}.value.selectDynamic($name)"
else q"${c.prefix.tree}.asInstanceOf[UnselectableFoo[$tpe]].${TermName(nameStr)}"
}
}