Strange behavior of type inference in function with upper bound

Sergey Passichenko

Ran into this strange behavior when changed upper bound in the implementation, but forgot to change it in the interface. I think last statement should not compile, but it does and returns unexpected result.

trait SuperBase
trait Base extends SuperBase

class SuperBaseImpl extends SuperBase

trait Service {
  def doWork[T <: Base : Manifest](body: T => Unit): String
  def print[T <: Base : Manifest]: String
}

object ServiceImpl extends Service {
  override def doWork[T <: SuperBase : Manifest](body: T => Unit): String =
    print[T]
  def print[T <: SuperBase : Manifest]: String =
    manifest[T].runtimeClass.toString
}

val s: Service = ServiceImpl

// does not compile as expected
// s.print[SuperBaseImpl]

// returns "interface Base"
s.doWork { x: SuperBaseImpl => () }

Edit

As @som-snytt mentioned with -Xprint:typer option we can see what compiler actually infers:

s.doWork[Base with SuperBaseImpl]

This explains why we are getting "interface Base". But I still not quite understand how and why type inference work in this case.

bdew

Note that what your code is saying is:

Method ServeImp.doWork MUST accept a parameter that is "a function that must accept some class T that is a sublass of Base and Superbase"

SuperBaseImpl is not a subclass of Base, but it's not an error because there could exist a class X which "extends SuperBaseImpl with Base" which would satisfy that requirement.

When type inference happens, T is resolved to "foo.Base with foo.SuperBaseImpl" which satisfies all requirements above. runtimeClass is interface Base because there is no way to describe that type in the JVM at runtime, but if you do manifest.toString - you will see the correct type.

There is no real way to demonstrate that with your example, but consider the following:

trait SuperBase
trait Base extends SuperBase

class SuperBaseImpl(val a: String) extends SuperBase

trait Service {
  def doWork[T <: Base : Manifest](body: T => String): (T) => String
}

object ServiceImpl extends Service {
  override def doWork[T <: SuperBase : Manifest](body: T => String): (T) => String =
    x => "Manifest is '%s', body returned '%s'".format(manifest[T].toString(), body(x))
}

val s: Service = ServiceImpl

val f = s.doWork { x: SuperBaseImpl => x.a }
// f: Base with SuperBaseImpl => String = <function1>

f(new SuperBaseImpl("foo") with Base)
// res0: String = Manifest is 'Base with SuperBaseImpl', body returned 'foo'

f(new SuperBaseImpl("foo"))
// compile error 

Here I've made doWork return another function that accepts T and you can see what it resolved to, and that you can actually call that and it will work correctly if you pass something that matches the constraints on all the types.

Added:

Also note that your class hierarchy is not necessary to show that behavior at all, they can be entirely unrelated.

trait A
trait B

def m[T <: A : Manifest](body: T => Unit) = manifest[T].toString()

m((x: B) => Unit)
//res0: String = A with B

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related