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

named arguments can't be used for implicit extension method if original type has same-named method #7809

Open
scabug opened this issue Sep 3, 2013 · 7 comments

Comments

@scabug
Copy link

scabug commented Sep 3, 2013

class A {
  def a(s: String) {}
}

object O {
  implicit class B(val a: A) {
    def a(i: Int) {}
    def b(i: Int) {}
  }
}

import O._

new A().a("")    // works
new A().a(1)     // works
new A().b(i = 1) // works
new A().a(i = 1) // does not work

The last line gives the following error message (Scala 2.10.2):
<console>:25: error: not found: value i
              new A().a(i = 1) // does not work
@scabug
Copy link
Author

scabug commented Sep 3, 2013

Imported From: https://issues.scala-lang.org/browse/SI-7809?orig=1
Reporter: @retronym
Affected Versions: 2.10.0, 2.11.8, 2.12.1

@scabug
Copy link
Author

scabug commented Mar 6, 2017

Justin Bailey (m4dc4p) said (edited on Mar 25, 2017 8:43:54 PM UTC):
To motivate fixing this bug, consider the following - automatically converting nullable types to Option:

case class AuthorizationRequest(
  val sid: String,
  val oid: Option[String],
  val o_class: Option[String],
  val act: Option[String],
  val ctx: Option[String]
)

trait AuthorizationClient {
  def authorize(req: AuthorizationRequest): Task[Unit]
}

object AuthorizationClient {
  implicit class AuthorizationExts(client: AuthorizationClient) {
    def authorize(sid: String, oid: String = null, o_class: String = null, act: String = null, ctx: Json = null) = {
      client.authorize(AuthorizationRequest(sid = sid, oid = Option(oid), o_class = Option(o_class), act = Option(action), ctx = Option(ctx)))
    }
  }
}

The intermediate Request type just adds verbosity, so the authorize method on AuthorizationExts makes it easy to call authorize. But authorize is long enough (and its arguments have the same type) that it would be nice to call it with named arguments:

auth.authorize(sid ="foo", oid = "bar", o_class = "bif", etc.) 

This also makes it impossible to omit parameters and use defaults instead. Self-contained example in my next comment.

@scabug
Copy link
Author

scabug commented Mar 25, 2017

Justin Bailey (m4dc4p) said (edited on Mar 25, 2017 8:50:12 PM UTC):
Here is a minimal example:

object S {
  class Test {
    def test(arg: Int) = ???
  }

  class ExtClass(f: Test) {
    def test(arg: String) = ???
  }

  implicit final def Implicit(t: Test) = new ExtClass(t)

  (new Test).test("works")
  (new Test).test(arg = "fails")
}

Note that test("works") compiles, while test(arg = "fails") does not.

I ran with -Ytyper-debug (2.12.2). Full output below, but I believe the key line (from test(arg = "fails")) is this:

no second try: (arg: Int)Nothing and arg = "fails" because error not in result: source-C:\Users\Justin\Source\SI-7809\S.scala,line-13,offset=246!=source-C:\Users\Justin\Source\SI-7809\S.scala,line-13,offset=239

This is the full typer-debug output for test(arg = "fails"):

|    |-- new Test().test(arg = "fails") BYVALmode-EXPRmode (site: value <local S> in S)
|    |    |-- new Test().test BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local S> in S)
|    |    |    |-- new Test() EXPRmode-POLYmode-QUALmode (silent: value <local S> in S)
|    |    |    |    |-- new Test BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local S> in S)
|    |    |    |    |    |-- new Test EXPRmode-POLYmode-QUALmode (silent: value <local S> in S)
|    |    |    |    |    |    |-- Test FUNmode-TYPEmode (silent: value <local S> in S)
|    |    |    |    |    |    |    \-> S.Test
|    |    |    |    |    |    \-> S.Test
|    |    |    |    |    \-> ()S.Test
|    |    |    |    \-> S.Test
|    |    |    \-> (arg: Int)Nothing
|    |    |-- "fails" : pt=Int BYVALmode-EXPRmode (silent: value <local S> in S)
|    |    |    [search #4] start `"fails"`, searching for adaptation to pt=String("fails") => Int (silent: value <local S> in S) implicits disabled
|    |    |    [search #5] start `"fails"`, searching for adaptation to pt=(=> String("fails")) => Int (silent: value <local S> in S) implicits disabled
|    |    |    \-> <error>
|    |    no second try: (arg: Int)Nothing and arg = "fails" because error not in result: source-C:\Users\Justin\Source\SI-7809\S.scala,line-13,offset=246!=source-C:\Users\Justin\Source\SI-7809\S.scala,line-13,offset=239
\

And this is from test("works"):

|    |-- new Test().test("works") BYVALmode-EXPRmode (site: value <local S> in S)
|    |    |-- new Test().test BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local S> in S)
|    |    |    |-- new Test() EXPRmode-POLYmode-QUALmode (silent: value <local S> in S)
|    |    |    |    |-- new Test BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local S> in S)
|    |    |    |    |    |-- new Test EXPRmode-POLYmode-QUALmode (silent: value <local S> in S)
|    |    |    |    |    |    |-- Test FUNmode-TYPEmode (silent: value <local S> in S)
|    |    |    |    |    |    |    \-> S.Test
|    |    |    |    |    |    \-> S.Test
|    |    |    |    |    \-> ()S.Test
|    |    |    |    \-> S.Test
|    |    |    \-> (arg: Int)Nothing
|    |    |-- "works" : pt=Int BYVALmode-EXPRmode (silent: value <local S> in S)
|    |    |    [search #1] start `"works"`, searching for adaptation to pt=String("works") => Int (silent: value <local S> in S) implicits disabled
|    |    |    [search #2] start `"works"`, searching for adaptation to pt=(=> String("works")) => Int (silent: value <local S> in S) implicits disabled
|    |    |    \-> <error>
|    |    second try: (arg: Int)Nothing and "works"
|    |    [search #3] start `()S.Test`, searching for adaptation to pt=S.Test => ?{def test(x$1: ? >: String("works")): ?} (silent: value <local S> in S) implicits disabled
|    |    [search #3] considering Implicit
|    |    |-- Implicit BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local S> in S) implicits disabled
|    |    |    \-> (t: S.Test)S.ExtClass
|    |    [adapt] Implicit adapted to (t: S.Test)S.ExtClass based on pt S.Test => ?{def test(x$1: ? >: String("works")): ?}
|    |    [search #3] success inferred value of type S.Test => ?{def test(x$1: ? >: String("works")): ?} is SearchResult(S.this.Implicit, )
|    |    |-- (t: S.Test)S.ExtClass EXPRmode-POLYmode-QUALmode (site: value <local S> in S)
|    |    |    \-> S.ExtClass
|    |    |-- S.this.Implicit(new S.this.Test()).test BYVALmode-EXPRmode-FUNmode-POLYmode (silent: value <local S> in S)
|    |    |    \-> (arg: String)Nothing
|    |    \-> Nothing

@scabug
Copy link
Author

scabug commented Mar 27, 2017

Justin Bailey (m4dc4p) said (edited on Apr 3, 2017 6:48:07 PM UTC):
This error is due to the existence of a AssignOrNamedArg node (https://github.com/scala/scala/blob/2.12.x/src/reflect/scala/reflect/internal/Trees.scala#L444) in the args Tree here - https://github.com/scala/scala/blob/2.12.x/src/compiler/scala/tools/nsc/typechecker/Typers.scala#L4602.

Filtering out the AssignOrNamedArg to the rhs value (i.e., the value assigned) makes the implicit resolution work, but causes other problems.

PR in progress at scala/scala#5807.

@scabug
Copy link
Author

scabug commented Apr 3, 2017

Justin Bailey (m4dc4p) said:
Research: Original PR for Named & Default Arguments at scala/scala@390ccac

@m4dc4p
Copy link

m4dc4p commented Apr 7, 2017

This bug is not a quick fix :(

@SethTisue SethTisue removed the quickfix label Apr 7, 2017
@som-snytt
Copy link

Progressed to "unknown parameter name".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants