scala3 icon indicating copy to clipboard operation
scala3 copied to clipboard

Unsafe unboxing conversion around function types?

Open e45lee opened this issue 3 years ago • 5 comments

Compiler version

PR #15768

Minimized code

class C {
  def bad() = println("I've gone bad!")
}

type Observe[T] = (T => Unit) -> Unit
def unsafe(cap: {*} C) = cap.bad()

def box[T](v: T) : Observe[T] = {
 (fn: T => Unit) => fn(v)
}

def main() : Int = {
  val boxed : Observe[{*} C] = box(new C)
  boxed(unsafe)

  0
}

Output

It compiles.

Expectation

Ondrej and I were discussing this, and we think this is a bug as somewhere the boxed {*} C in the Observe[box {*} C] type is getting unboxed, we think.

e45lee avatar Jul 27 '22 19:07 e45lee

Where do you think an error should be reported?

new C has type C, so it is pure. I believe that's why things typecheck. On the other hand, if I replace new C with newC where

def newC: {*} C = C()

I get

-- [E007] Type Mismatch Error: i15772.scala:15:36 ------------------------------
15 |   val boxed : Observe[{*} C] = box(newC)
   |                                    ^^^^
   |                                    Found:    {*} C
   |                                    Required: box {*} C

odersky avatar Aug 08 '22 14:08 odersky

I would've thought that would've occurred when calling unsafe, in the original example. I'm actually surprised this gives a typing error, as wouldn't we implicitly box newC here in this case?

e45lee avatar Aug 08 '22 17:08 e45lee

You can't box if the capture set is *.

odersky avatar Aug 08 '22 21:08 odersky

Fair enough, shouldn't there still be a compilation error at unboxing as we don't know if the boxed value is pure? Here's a revised example where we box something impure, and unsafe is still allowed to be invoked:

class C(arg: {*} C) {
  def bad() = println("I've gone bad!")
}

def main(x: {*} C) : Int = {
  val c : {x} C = new C(x)
  val boxed : Observe[{*} C] = box(c)
  boxed(unsafe)

  0
}

e45lee avatar Aug 09 '22 11:08 e45lee

I think that's because the whole thing is in a global method. Methods don't capture anything. If you make main a function literal, you'll find in #15768 that the literal does capture c and x.

odersky avatar Aug 10 '22 09:08 odersky