Dotty Union types as a replacement for Open Union

(moving what I posted on Gitter here.)
Hi Everyone,

I’m just reading a bit more about scala 3’s Union Type (Union Types | Scala 3 — Book | Scala Documentation),
and the doc states that “The compiler assigns a union type to an expression only if such a type is explicitly given.” – is there a way to for the compiler to eliminate a type from a union? I am trying to see how I can use dotty’s union type to simulate an open-union type. The compiler returns an Object instead of the rest of the union, and I am having a hard time.

For instance, If we look at the code below (extensible effects):

  extension [R[_], E, A](eff: Eff[[A] =>> Reader[E, A] | R[A], A])
    def runReader(value: E): Eff[R, A] = 
      handleRelay[[A] =>> Reader[E, A], R, A, A]
        (a => Pure(a))
        ([T] => (r: Reader[E, T]) => (k: Arr[R, T, A]) => r match { case ReaderL() => k(value) })

The return type is Eff[R, A] where R is in the union Reader[E, A] | R[A], what the compiler will do is return the supertype of R as stated in the document, which in this case is Object. So, the next function that will apply the output of runReader will apply it to this type Eff[[X] => Object, A]. Unless, I explicitly define a type for it.

so, the example below will not work because of what I stated above.

type MyEffects = [A] =>> Reader[Int, A] | Writer[String, A]

val interpreter: Eff[MyEffects, Int] = for 
  i  <- Reader.ask[Int]
  _ <- Writer.tell(s"Environment: ${i}")
yield i

val program = 
    .runWriter  // Will fail here because the compiler infer the return type of runReader as 
               // `Eff[[X] => Object, Int]` when it should be `Eff[[X] => Writer[String, X], Int]`

However, If I explicitly define the return type of runReader, it will compile but it doesn’t compose well.

val program = 
  val x: Eff[[A] =>> Writer[String, A], Int] = interpreter.runReader(100)

How can I achieve this using dotty’s union types? Or, is it just impossible?