2
votes
object Test {

  trait Foo

  trait TC[A]

  object TC {
    implicit def tc1[F <: Foo] = new TC[F] {}
    implicit def tc2[F1 <: Foo, F2 <: Foo] = new TC[(F1, F2)] {}
  }   

  object Bar {
    trait X
    val c = new Foo with X
    def bar[A](a: this.type => A)(implicit tc: TC[A]) = 1
  }

  Bar bar (_.c)

  Bar bar (b => (b.c, b.c))
}

The last line gives compiler error "could not find implicit value for parameter tc...".

Now: moving trait X outside of object Bar makes it work.

The next to last line works in both cases.

Is there any good reason for this, and/or is it possible to make it work without moving the trait out of the object?

1

1 Answers

2
votes

This is not going to be a full blown answer (and contains a high dose of speculation), but given that you got none until now I imagine that it's better than nothing.

It seems that the problem lies with the fact that the compiler treats X in new Foo with X as a path-dependant type, even though we are in an object definition rather than a class. Because of this, the compiler then infers A = (Test.Foo with b.X, Test.Foo with b.X) forSome { val b: Test.Bar.type } in the second call to bar. This requires the compiler to find an implicit value of type TC[(Test.Foo with b.X, Test.Foo with b.X) forSome { val b: Test.Bar.type }], and apparently tc2 is no fit (I am not intimate enough with the corner cases fo the scala type system to know for sure if there is a genuine incompatibility, or if the compiler just has no clue)

[speculation mode]

The problem with the path-dependent type handling smells like a bug to me (or at least an underspecified oddity). I think that the culprit is that the body of an object is compiled no different from a class, and then is somehow made into a singleton object, which means that new Foo with X is really seen as new Foo with this.X instead of new Foo with Bar.X, and thus treated as a path dependent type (even though they should IMHO denote the very same thing in this case).

[/speculation mode]

And now for the weird part (which is also the work around that you requested): turning this:

val c = new Foo with X

into this:

val c = new Foo with Bar.X

actually fixes the compilation. As I understand it, this is because by explicitly specifying Bar we force the compiler to recognize that Bar.X is a stable path, which clears the problem with path dependent types.

Now, the real work around is of course to really move X outside Bar as you suggested. It works and is painless, so why not do it?