scala/scala3

There is no way to preserve a type refinement with using/given clauses

Closed this issue · 5 comments

Compiler version

3.6.3

Minimized code

class MySelectable(values: Seq[(String, Any)]) extends Selectable:
  def selectDynamic(name: String): Any = values.collectFirst {
    case (k, v) if k == name => v
  }.get
  
object MySelectable:
  transparent inline given derived: MySelectable =
    MySelectable(Seq("hello" -> "world")).asInstanceOf[MySelectable { def hello: String }]

// this does not compile
//def test(using s: MySelectable): String = s.hello

// but this works fine
val res: String = summon[MySelectable].hello
println(res)

Have you tried def test[S <: MySelectable](using s: S): String = s.hello?

error output for context:

-- [E008] Not Found Error: ~/compiler-repro/repro.scala:11:44 
11 |def test(using s: MySelectable): String = s.hello
   |                                          ^^^^^^^
   |                             value hello is not a member of MySelectable
1 error found

I believe this is expected not to work. By declaring (using s: MySelectable) as a parameter of the method you tell the compiler that the type of s inside the body of def test is just MySelectable, nothing more. The compiler cannot assume that it's MySelectable with some refinement because at the place where test would be invoked later on there could be another given instance of MySelectable available which had no refinement

I, too, am a bit surprised there is no way to make it work.

given () => (MySelectable { def hello: String }) = ???
transparent inline def hello(using MySelectable) = ???
transparent inline def hellno(inline f: MySelectable ?=> String) = ???

The intuition is that it compiles if the expansion compiles, but that is not what we want. Also, if the contextual parameter were inlined, would you want lazy val semantics or what. Is the feature conditional compilation or inline match.

Oh, I guess that works.

inline def hello(using MySelectable): String =
  inline summon[MySelectable] match
    case x: MySelectable { def hello: String } => s"hello, ${x.hello}"
    case _ => "goodbye"

@main def test = println:
  // given MySelectable = MySelectable(Seq("goodbye" -> "cruel")) // nullify the default given
  hello

Per the OP expectation, it also works with

transparent inline given MySelectable = ???

Have you tried def test[S <: MySelectable](using s: S): String = s.hello?

Yeah same issue

Image

The compiler cannot assume that it's MySelectable with some refinement because at the place where test would be invoked later on there could be another given instance of MySelectable available which had no refinement

That does makes sense.

Oh, I guess that works.

And very cool. Thanks guys for the ideas.