-
Notifications
You must be signed in to change notification settings - Fork 21
Description
There is no way to define a companion object abstractly which retains the quality of being a companion. The language does not treat a val (abstract or not) as a companion, even when defined with the same name in the same scope. The implication of this is that code structured as we have structured the reflection api makes sane implicit management inaccessible.
package s1 {
trait AbDingos {
type Dingo
val Dingo: DingoCompanion
abstract class DingoCompanion {
implicit def string2dingo(s: String): Dingo
}
}
object Dingos extends AbDingos {
class Dingo
object Dingo extends DingoCompanion {
implicit def string2dingo(s: String): Dingo = new Dingo
}
}
class A {
def f1(x: AbDingos) = { import x._ ; import Dingo._ ; "a": Dingo } // ok
def f2(x: Dingos.type) = { import x._ ; "a": Dingo } // ok
def f3(x: AbDingos) = { import x._ ; "a": Dingo } // fail
// ./a.scala:18: error: type mismatch;
// found : String("a")
// required: x.Dingo
// def f3(x: AbDingos) = { import x._ ; "a": Dingo }
// ^
// one error found
}
}
Since "val Dingo: DingoCompanion" is not seen as a companion to "type Dingo", the companion scope is not searched for a String => Dingo implicit. Concretely, this makes it impossible to move the String => TermName and String => TypeName implicits into the companion objects where they belong (that for the reasons enumerated in that pull request - scala/scala#2371).
Compounding the inconsistency is what we see if I make a small variation to the code above; define "class Dingo" within AbDingos, and remove the class declaration from Dingos.
package s2 {
trait AbDingos {
class Dingo
val Dingo: DingoCompanion
abstract class DingoCompanion {
implicit def string2dingo(s: String): Dingo
}
}
object Dingos extends AbDingos {
object Dingo extends DingoCompanion {
implicit def string2dingo(s: String): Dingo = new Dingo
}
}
class A {
def f1(x: AbDingos) = { import x._ ; import Dingo._ ; "a": Dingo } // ok
def f2(x: Dingos.type) = { import x._ ; "a": Dingo } // fail
def f3(x: AbDingos) = { import x._ ; "a": Dingo } // fail
// ./a.scala:46: error: type mismatch;
// found : String("a")
// required: x.Dingo
// def f2(x: Dingos.type) = { import x._ ; "a": Dingo } // fail
// ^
// ./a.scala:47: error: type mismatch;
// found : String("a")
// required: x.Dingo
// def f3(x: AbDingos) = { import x._ ; "a": Dingo } // fail
// ^
}
}
Now even the completely concrete variation fails, because "class Dingo" is defined together with abstract "val Dingo", and object Dingo is defined in a subclass which inherits class Dingo.
The superficially obvious answer to all of it is to generalize companion semantics to cover abstract types and abstract vals. It would be more regular anyway. Maybe there is some type-theoretic reason not to do it, but I don't see it offhand. But either we have to do that or the entire approach taken in the reflection api should be reconsidered.
Here's a typical example, one of dozens chosen at random: type ThisType and val ThisType. They aren't companions, and without companion semantics there is no way to write companion-scoped implicits at any abstract layer.
type ThisType >: Null <: AnyRef with SingletonType with ThisTypeApi
val ThisType: ThisTypeExtractor
abstract class ThisTypeExtractor {
// we could write an implicit Foo => ThisType here
// and it would never be found
def apply(sym: Symbol): Type
def unapply(tpe: ThisType): Option[Symbol]
}