Skip to content

companion requirements make abstract definition impossible #7348

@scabug

Description

@scabug

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]
  }

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions