18
votes

Why does this code fail to compile, but compiles successfully when I uncomment the indicated line? (I'm using Scala 2.8 nightly). It seems that explicitly calling string2Wrapper allows it to be used implicitly from that point on.

class A {
  import Implicits.string2Wrapper
  def foo() {
     //string2Wrapper("A") ==> "B" // <-- uncomment
  } 
  def bar() {
    "A" ==> "B"
    "B" ==> "C"
    "C" ==> "D"
  }
  object Implicits {
    implicit def string2Wrapper(s: String) = new Wrapper(s)
    class Wrapper(s: String) {
      def ==>(s2: String) {}
    }
  }
}

Edit: thanks for the answers so far, which include a pointer to Martin Odersky's comment,

"An implicit conversion without explicit result type is visible only in the text following its own definition. That way, we avoid the cyclic reference errors."

I'd still be interested in finding out 1) what is the danger of "cyclic reference errors"?, and 2) Why does an explicit call make any difference?

3

3 Answers

21
votes

Explicitly ascribing the return type of string2Wrapper fixes the problem.

class A {
  import Implicits._

  def bar() {    
    "A" ==> "B"
    "B" ==> "C"
    "C" ==> "D"
  }
  object Implicits {
    implicit def string2Wrapper(s: String): Wrapper = new Wrapper(s)
    class Wrapper(s: String) {
      def ==>(s2: String) {}
    }
  }
}

Defining Implicits before bar also works:

class A {
  object Implicits {
    implicit def string2Wrapper(s: String) = new Wrapper(s)
    class Wrapper(s: String) {
      def ==>(s2: String) {}
    }
  }

  import Implicits._

  def bar() {    
    "A" ==> "B"
    "B" ==> "C"
    "C" ==> "D"
  } 
}

If you need to rely on a implicit conversion defined below within the current scope, make sure you annotate its return type. Pretty sure this has come up on the mailing lists before, and may be expected behaviour rather than a bug. But I can't locate it at the moment. I guess the explicit call in foo triggers the type inference of the return type of bar, which is then valid when typing the contents of bar.

UPDATE

What is the danger of the cyclic reference error?

The body of the implicit method may call the method that requires the implicit conversion. If both of these have an inferred return type, you're at an impasse. This doesn't apply in your example, but the compiler doesn't attempt to detect this.

Why does an explicit call make a difference?

The explicit call earlier triggers type inference of the return type of the implicit method. Here's the logic in Implicits.isValid

sym.isInitialized ||
      sym.sourceFile == null ||
      (sym.sourceFile ne context.unit.source.file) || 
      hasExplicitResultType(sym) ||
      comesBefore(sym, context.owner)

UPDATE 2

This recent bug looks relevant: https://lampsvn.epfl.ch/trac/scala/ticket/3373

12
votes

If you were just ooooone nightly later you would instead have seen the error message I added yesterday.

<console>:11: error: value ==> is not a member of java.lang.String
 Note: implicit method string2Wrapper is not applicable here because it comes after the application point and it lacks an explicit result type
           "A" ==> "B"
           ^
<console>:12: error: value ==> is not a member of java.lang.String
 Note: implicit method string2Wrapper is not applicable here because it comes after the application point and it lacks an explicit result type
           "B" ==> "C"
           ^
<console>:13: error: value ==> is not a member of java.lang.String
 Note: implicit method string2Wrapper is not applicable here because it comes after the application point and it lacks an explicit result type
           "C" ==> "D"
           ^
3
votes

If you put object Implicits first, it works. This looks like a bug to me in the logic for making multiple compiler passes; it assumes it can get away without really knowing about string2Wrapper when compiling bar. My guess is that if you use it, it knows it can't get away with not knowing what string2Wrapper really is, actually compiles Implicits, and then realizes that ==> is implicitly defined on String.

Edit: Based on what Retronym posted, maybe it's a "feature" not a bug. Still seems screwy to me!