How to disambiguate Java overloads taking long and Object?


#1

I’m using http://fastutil.di.unimi.it/docs/it/unimi/dsi/fastutil/longs/Long2ObjectMap.html from Scala 2.11.8. The same happens from 2.11.12. I’m using Long2ObjectMap, which has two remove methods.

V remove(final Object key); // from java.util.Map
V remove(final long key); // from it.unimi.dsi.fastutil.longs.Long2ObjectFunction

Unfortunately I’m not able to call remove because Scala thinks the first one takes Any (not AnyRef), the second takes long, which is also an Any, and so I get the error,

Error:(94, 15) ambiguous reference to overloaded definition,
both method remove in trait Long2ObjectMap of type (x$1: Any)V
and  method remove in trait Long2ObjectFunction of type (x$1: Long)V
match argument types (Long)
      builder.remove(x)

Is there a way to tell Scala to use the method that takes long? I can add : Any to my parameter to get it to use the (Object) one, but that’s not ideal. : Long doesn’t help.


#2

This is getting more and more interesting. I’ve tried it with Scala 2.12 and 2.10. Those don’t work either. The inheritance tree looks like

Long2ObjectOpenHashMap
  AbstractLong2ObjectMap
    AbstractLong2ObjectFunction
      Long2ObjectFunction
    Long2ObjectMap
      Long2ObjectFunction
      java.util.Map

The ones that compile for remove(long) in the above tree are,

val m: Long2ObjectOpenHashMap[V] = ...
m.remove(3L)
val m: Long2ObjectFunction[V] = ...
m.remove(3L)
val m: AbstractLong2ObjectFunction[V] = ...
m.remove(3L)

For some reason, Long2ObjectMap, and AbstractLong2ObjectMap do not compile. Based on that, I would expect that Long2ObjectOpenHashMap to not compile, but it does.


#3

Here’s a minimal example showing the problem, or close to it.

class Parent {
  def f(x: Any): Unit = ???
  def f(x: Long): Unit = ???
}

class ChildOverridesAny extends Parent {
  override def f(x: Any): Unit = ???
}

class ChildOverridesLong extends Parent {
  override def f(x: Long): Unit = ???
}

object Usage {
  // compiles
  (null: Parent).f(3L)
  // compiles
  (null: ChildOverridesLong).f(3L)
  // does not compile
  (null: ChildOverridesAny).f(3L)
}

#4

Long can be replaced with AnyVal to the same result.


#5

This isn’t specific to Any and AnyVal like it appeared. It applies to any overloaded defs where the type of the argument to one is the parent of the other, and you call the method taking the child on a class where the method taking the parent is overridden.

class X
class Y extends X

class Parent {
  def f(x: X): Unit = ???
  def f(x: Y): Unit = ???
}

class ChildOverridesX extends Parent {
  override def f(x: X): Unit = ???
}

class ChildOverridesY extends Parent {
  override def f(x: Y): Unit = ???
}

object Usage {
  // compiles
  (null: Parent).f(new Y)
  // does not compile
  (null: ChildOverridesX).f(new Y)
  // compiles
  (null: ChildOverridesY).f(new Y)
}

#6

I’ve opened a bug. https://github.com/scala/bug/issues/11456


#7

Thanks to @tarsa! https://contributors.scala-lang.org/t/proposal-make-overload-resolution-bind-more-closely-for-matching-types/2998/3?u=shawjef3

The key was to upcast my value to the type that contains the method I want to use before calling it.

So to get (null: ChildOverridesAny).f(3L) to compile:

((null: ChildOverridesAny): Parent).f(3L)

Or in my original code, change

val m: Long2ObjectOpenHashMap[V] = ...
m.remove(3L)

to

val m: Long2ObjectOpenHashMap[V] = ...
(m: Long2ObjectFunction[V]).remove(3L)

#8

Based on the som-snytt’s response quoted here you can workaround this problem overriding both methods.

… Scala says you can reason about an overloaded f without referring to the application itself, the use site. Given an overloaded f, it scores the two fs for which is “more specific.” That means taking a more specific parameter, or being defined in a subclass. Usually, subclassing means imposing more specific constraints. If the two fs score the same, then you can’t choose between them, so the overload is ambiguous.

class ChildOverridesAny extends Parent {

  override def f(x: Any): Unit = println("override any")
  override def f(x: Long): Unit = super.f(x)

}

object Usage {
  // compiles
  (new ChildOverridesAny).f(3L)
  (new ChildOverridesAny).f("foo")
}