selectDynamic via extension methods

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)}"
  }
}