effekt-lang / effekt

A language with lexical effect handlers and lightweight effect polymorphism
https://effekt-lang.org
MIT License
334 stars 24 forks source link

Unambiguous field access leads to a confusing 'Unbox requires a boxed type' error #670

Closed jiribenes closed 2 weeks ago

jiribenes commented 2 weeks ago

In the following program (already minified), there's an error "Unbox requires a boxed type, but got Person." over the person.age call:

interface Foo {
  def foo(): Unit
  def age(s: String): Int
}

record Person(name: String, age: Int)

def foo(person: Person) { prog: () => Unit / Foo }: Unit = {
  try { prog() } with Foo {
    def foo() = {
      val age: Int = person.age
      //             ~~~~~~
      // Unbox requires a boxed type, but got Person.

      if (age < 100) {
        println(person.name)
      }
      resume(())
    }
    def age(s) = resume(42)
  }
}

The error goes away if you remove the age operation. If you instead make the age operation into a separate function returning 42, then the overload becomes ambiguous with a nice error message:

Ambiguous overload.
There are multiple overloads, which all would type check:
- module0::age: Person => Int
- module0::age: Person => Int

In the original, the compiler is really saying that it cannot distinguish between the Foo::age operation and the Person::age field access. Not only is the error message a bit confusing (what is it unboxing?), but the type-based name resolution here should be unambiguous, I think, since:

  1. we've annotated the type Int, so the result of person.age must be an Int, which is only true for the Person::age(person) interpretation
  2. I don't think it could've been the age operation here anyways as we'd need to use the do keyword, something like do age(person)

Perhaps I'm misunderstanding something here, but for now, I'll use both the bug and the errormessage labels.

EDIT: Maybe this has something to do with a generated getter? (some age: Person => Int function generated by the compiler?)

jiribenes commented 2 weeks ago

@marzipankaiser noted that it could be because of some presumed {person: Foo} capability -> person.age and that you can do age(person) instead as a workaround.