Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fail to resolve overloaded method via implicit class with same name and different signature #10206

Closed
scabug opened this issue Feb 23, 2017 · 9 comments
Labels
Milestone

Comments

@scabug
Copy link

scabug commented Feb 23, 2017

As per this Stackoverflow question, following fails to compile:

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
}
-Ytyper-debug bonkers immediately after finding a suitable candidate upon 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

Perhaps indirectly related to SI-6760 but overloaded method had different signature than Object.clone.

This does compile under 2.10.6. Biggest difference I see in the type tree is that 2.10.6 tried to cook the method parameters while 2.11.8 doesn't:

2.11.8:

DefDef( // def clone(x: Int,y: Int): Int in class Enrich
 <method>
 "clone"

2.10.6:

DefDef( // def clone(x: Int,y: Int): Int in class Enrich
 <method> <triedcooking>
  "clone"
@scabug
Copy link
Author

scabug commented Feb 23, 2017

Imported From: https://issues.scala-lang.org/browse/SI-10206?orig=1
Reporter: Yuval Itzchakov (Yuval.Itzchakov)
Affected Versions: 2.11.8, 2.12.0, 2.12.1
See #6760

@scabug
Copy link
Author

scabug commented Feb 23, 2017

@adriaanm said:
This seems to be specific to the "clone" method. Could you use a different name as a workaround?

@scabug
Copy link
Author

scabug commented Feb 23, 2017

@adriaanm said (edited by @SethTisue on Feb 23, 2017 10:32:28 PM UTC):
Hah, nice one. This is because implicit conversions that target a superclass of AnyRef are ruled out:

        pt match {
          case Function1(_, out) =>
            // must inline to avoid capturing result
            def prohibit(sym: Symbol) = (sym.tpe <:< out) && { // BUG: when looking for implicit conversion to `?{def clone: ?}`, this triggers because AnyRef has that method
              maybeInvalidConversionError(s"the result type of an implicit conversion must be more specific than ${sym.name}")
              true
            }
            if (prohibit(AnyRefClass) || (settings.isScala211 && prohibit(AnyValClass)))
              result = SearchFailure
          case _                 => false
        }

@scabug
Copy link
Author

scabug commented Feb 23, 2017

@adriaanm said:
scala/scala#5736

@scabug
Copy link
Author

scabug commented Feb 24, 2017

Yuval Itzchakov (Yuval.Itzchakov) said (edited on Feb 24, 2017 8:42:41 AM UTC):
Thanks a lot for the quick fix Adriaan!

Would you mind elaborating how/why a structural type supertyping AnyRef is created here?

@scabug
Copy link
Author

scabug commented Feb 24, 2017

Jasper-M said (edited on Feb 24, 2017 9:44:52 AM UTC):
Consider this REPL transcript:

scala> class Foo { def apply(a: Int, b: Int) = (a,b) }
defined class Foo

scala> implicit class IntFoo(i: Int) { def foo = new Foo }
defined class IntFoo

scala> 1.foo(2,3)
res1: (Int, Int) = (2,3)

So when you call foo.clone(1, 2) an implicit conversion to structural type

?{ def clone: ? }

has to be considered. And that is a supertype of {{AnyRef}}.

@scabug
Copy link
Author

scabug commented Feb 24, 2017

Yuval Itzchakov (Yuval.Itzchakov) said (edited on Feb 24, 2017 10:42:57 AM UTC):
Jasper, perhaps I'm missing something here.

When you write

1.foo(2,3)

Why does an implicit conversion to a structural have to be considered? I would expect that an implicit conversion on Int to be looked up and searched for a method called clone, but I do not understand where structural comes in.

To add to the question, even if there is a structural type to has to be looked up, why is it a supertype of AnyRef and not a subtype?

@scabug
Copy link
Author

scabug commented Feb 24, 2017

Jasper-M said:
To be clear, I don't know the internals of the compiler. But it is my understanding that when I call 1.foo and the compiler sees that Int doesn't have a foo method, it will look for an implicit value that conforms to

Int => ?{def foo: ?}

because it doesn't know anything about the required result type other than that it should have a method called foo.
And in the clone case, AnyRef had a method def clone(): AnyRef and also a bunch of other methods, so that makes AnyRef a subtype of the structural type that contains only a method def clone: ?.

Now aside of that, I think the compiler uses some extra internal mechanisms other than pure basic userfacing implicit search, because

scala> implicitly[AnyRef <:< Any{def clone: Any}]
<console>:8: error: Cannot prove that AnyRef <:< Any{def clone: Any}.
              implicitly[AnyRef <:< Any{def clone: Any}]
                        ^

scala> implicitly[AnyRef <:< Any{def clone(): Any}]
res1: <:<[AnyRef,Any{def clone(): Any}] = <function1>

@scabug
Copy link
Author

scabug commented Feb 24, 2017

Yuval Itzchakov (Yuval.Itzchakov) said (edited on Feb 24, 2017 3:55:36 PM UTC):
@Jasper Thanks for that explanation, that makes sense.

Just to clear my mind, when we say "structural type", we're talking about some structure of a type and not explicitly about the Scala feature called structural types, right? We just need a way to describe a shape of a type we don't know yet.

Regarding your example in the code, what is that trying to convey? Not sure I follow. I would imagine the first one doesn't work is it doesn't match the structure of Object.clone.

@scabug scabug closed this as completed Mar 21, 2017
@scabug scabug added this to the 2.11.9 milestone Apr 7, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant