Skip to content

implicit search does not account for singleton types #3964

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

Closed
scabug opened this issue Oct 26, 2010 · 6 comments
Closed

implicit search does not account for singleton types #3964

scabug opened this issue Oct 26, 2010 · 6 comments

Comments

@scabug
Copy link

scabug commented Oct 26, 2010

One of those issues I've been sitting on forever and talking to adriaan makes me realize that maybe everyone else doesn't cry for half an hour every morning about it and that the mere act of opening a ticket might make a difference.

And so! I believe this code should work:

object Test {
  object Bob
  class Foo { def bippy = 42 }
  
  implicit def mkFoo(x: Bob.type): Foo = new Foo
  
  def main(args: Array[String]): Unit = {
    println(Bob.bippy)
  }
}
// actual outcome:
// a.scala:8: error: value bippy is not a member of object Test.Bob
//     println(Bob.bippy)
//                 ^
// one error found

And were it to work, I would also hope for this to work, as it does for the analogous case for normal classes:

object Test {
  class Base 
  object Bob extends Base
  class Foo { def bippy = 42 }
  class Oof { def bippy = -21 }
  
  // I am more specific than you
  implicit def f1(x: Bob.type): Foo = new Foo
  implicit def f2(x: Base): Oof = new Oof
  
  def main(args: Array[String]): Unit = {
    // this would of course print an unambiguous 42
    println(Bob.bippy)
  }
}

I am familiar with #1208 and the fact that singleton types will not sing and dance with wild abandon. I'm hoping this situation doesn't fall under the general umbrella both because it would be super useful, and because it would mean implicit search doesn't use normal function application rules, as visible here:

object Test {
  object Bob
  class Foo { def bippy = 42 }
  val Dingus: Bob.type = Bob

  implicit def f1(x: Bob.type): Foo = new Foo    
  
  def main(args: Array[String]): Unit = {
    // none of these expressions would work implicitly...
    // ...but in all cases the receiver can be passed to f1 explicitly.
    f1(Bob).bippy
    f1(Bob: Bob.type).bippy
    f1(Dingus).bippy
    f1(Dingus: Bob.type).bippy
  }
}
@scabug
Copy link
Author

scabug commented Oct 26, 2010

Imported From: https://issues.scala-lang.org/browse/SI-3964?orig=1
Reporter: @paulp

@scabug
Copy link
Author

scabug commented Oct 27, 2010

@paulp said:
(In r23383) A double goodness whammy involving type inference at the borders.

  1. Implicit search preserves singleton type fidelity.
  2. Unification of parent bounds is (closer to) correct.

Result of 1: "implicit def f(x: Foo.type)" will convert object Foo.
Result of 2: "new Trait[Int] { }" may enjoy its type constructor being
inferred, no longer foiled by the anonymous class.

Also included are some clarity-enhnancing renamings and refactorings.
Performance note: I heavily benchmarked the change to isSubArgs and it
is reproducibly faster than the previous implementation. Numbers
and methodology available upon request.

Closes #2693, #3964. Review by moors, who wrote most of this patch
but might like to review the comments.

@scabug scabug closed this as completed May 18, 2011
@scabug
Copy link
Author

scabug commented May 10, 2012

@Blaisorblade said:
I tried similar code, but it failed on 2.9.1 and 2.10.0-M3:

scala> val a: String = ""
a: String = ""

scala> case class Marked(a: String)
defined class Marked

scala> implicit def foo(b: String): Marked = Marked(b)
warning: there were 1 feature warnings; re-run with -feature for details
foo: (b: String)Marked

scala> def ctx(c: Marked) = c
ctx: (c: Marked)Marked

scala> ctx(a)
res0: Marked = Marked()

scala> implicit def bar(b: a.type): Marked = null
warning: there were 1 feature warnings; re-run with -feature for details
bar: (b: a.type)Marked

scala> ctx(a) //This is expected to return null because the implicit conversion bar should be applied; conversions however are not applied in the right order.
res1: Marked = Marked()

scala> ctx(bar(a)) //This expansion should be considered (and make the code ambiguous) or applied (because it is more specific).
res2: Marked = null

scala> ctx(a: a.type)
res3: Marked = null

@scabug
Copy link
Author

scabug commented May 10, 2012

@paulp said:
Strange as it may sound, a doesn't have type a.type. It has the type you gave it, String.
Try declaring like this:

final val a = ""

@scabug
Copy link
Author

scabug commented May 10, 2012

@Blaisorblade said:
That works, so sorry for the noise and thanks for the answer.

However, I'm confused by the use of 'final' for this, since final in Java marks immutable values and Scala doesn't change so much the meaning of existing syntax, hence it seems redundant with val. I just learned, though, that 'final val' has a specific meaning already in Scala 2.9 - which does not make it less confusing, just already existing.

@scabug
Copy link
Author

scabug commented May 11, 2012

@paulp said:
a) final is not redundant for vals regardless because non-final vals can be overridden.
b) In java final means immutable (unenforced), not overridable, not extendable, and also changes synchronization semantics. It's a busy word.
c) This aspect of scala final is not new to 2.9, it goes back to at least 2.7.
d) None of this is to say it is ideal. It makes a certain amount of sense when you see how the pieces fit together, but nobody has ever been unsurprised to discover it.

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

No branches or pull requests

1 participant