3
votes

I get an unexpected compilation error (in 2.11.8) when attempting to make an implicit class with a method named clone.

The following simplified usage:

class Foo(val bar: String)

object Foo {
  implicit class Enrich(foo: Foo) {
    def clone(x: Int, y: Int): Int = x + y
  }
}

object Main extends App {
  val foo = new Foo("hello")
  println(foo.clone(1, 2))    // <- does not compile
}

generated the following error:

method clone in class Object cannot be accessed in Foo Access to protected method clone not permitted because prefix type Foo does not conform to object Main where the access take place

However, I can manually apply the implicit class and it successfully compiles:

println(Foo.Enrich(foo).clone(1, 2))    // <- OK

If I rename the method to something else (clone2, for example) the code compiles as expected.

I assume this is somehow related to the magic around java.lang.Cloneable, but that method does not expect to parameters.

So what is going on here?

2

2 Answers

5
votes

This has to do with the fact Object (or AnyRef in Scala) posses a protected method clone() which takes precedence in overload resolution of Foo.

SI-6760 partially talks about this problem, although clone there has an identical signature while in this case it is different.

This feels like a bug (and now open as SI-10206). When we expand the typer tree with -Ytyper-debug, you can see that it finds a suitable candidate for def clone(int, int), but then fails in the immediate subsequent search:

|-- foo.clone(1, 2) : pt=Unit EXPRmode (site: method main in Main)
|    |    |    |    |-- foo.clone BYVALmode-EXPRmode-FUNmode-POLYmode (silent: method main in Main)
|    |    |    |    |    |-- foo EXPRmode-POLYmode-QUALmode (silent: method main in Main)
|    |    |    |    |    |    \-> foo.type (with underlying type my.awesome.pkg.Foo)
|    |    |    |    |    [search #1] start `my.awesome.pkg.Foo`, searching for adaptation to pt=foo.type => ?{def clone: ?} (silent: method main in Main) implicits disabled
|    |    |    |    |    |-- my.awesome.pkg.Foo.Enrich TYPEmode (site: method Enrich in Foo)
|    |    |    |    |    |    \-> my.awesome.pkg.Foo.Enrich
|    |    |    |    |    |-- Foo TYPEmode (site: value foo in Foo)
|    |    |    |    |    |    \-> my.awesome.pkg.Foo
|    |    |    |    |    |-- Int TYPEmode (site: method clone in Enrich)
|    |    |    |    |    |    \-> Int
|    |    |    |    |    |-- Int TYPEmode (site: value x in Enrich)
|    |    |    |    |    |    \-> Int
|    |    |    |    |    |-- Int TYPEmode (site: value y in Enrich)
|    |    |    |    |    |    \-> Int
|    |    |    |    |    [search #1] considering pkg.this.Foo.Enrich
|    |    |    |    |    |-- pkg.this.Foo.Enrich BYVALmode-EXPRmode-FUNmode-POLYmode (silent: method main in Main) implicits disabled
|    |    |    |    |    |    \-> (foo: my.awesome.pkg.Foo)my.awesome.pkg.Foo.Enrich
|    |    |    |    |    [search #1] success inferred value of type foo.type => ?{def clone: ?} is SearchResult(pkg.this.Foo.Enrich, )
|    |    |    |    |    [search #2] start `my.awesome.pkg.Foo`, searching for adaptation to pt=(=> foo.type) => ?{def clone: ?} (silent: method main in Main) implicits disabled
|    |    |    |    |    \-> <error>
Main.scala:6: error: method clone in class Object cannot be accessed in my.awesome.pkg.Foo
 Access to protected method clone not permitted because
 prefix type my.awesome.pkg.Foo does not conform to
 object Main in package pkg where the access take place
    foo.clone(1, 2) // <- does not compile

Edit

This does compile under 2.10.6

0
votes

Seems like a bug with the way Scala masks java.lang.Object with AnyRef. I was tempted to say the difference between protected for Scala and Java could be the culprit (Java's being more visible), but this example works just fine:

public abstract class Bar {
    protected Object method() {
         return null;
    }
}

case class Foo(bar: String) extends Bar

object Example {
    implicit class Enrich(foo: Foo) {
        def method(x: Int, y: Int): Int = x + y
    }

    Foo("abc").method(1, 3)
}

You an work around this issue by overriding clone() to make it public, presuming you don't really need it.

class Foo(val bar: String) {
    override def clone() = this
}

implicit class Enrich(foo: Foo) {
    def clone(x: Int, y: Int): Int = x + y
}

scala> new Foo("abc").clone(1, 2)
res0: Int = 3