Search code examples
scalagenericsinheritanceself-type

How to automatically inherit mixin generic type in self-types?


How do I inherit generic type from parent mixin type? For example I have a trait Foo with one generic type A:

trait Foo[A] {
  def value: A
}

I have a class User that uses Foo[String], like:

class User extends Foo[String] {
  override def value: String = ???
}

Everything works fine. Now, I want to add a trait Bar[A] with self-type of Foo[A].

trait Bar[A] { self: Foo[A] =>
  def anotherValue: A
}

If I want to use Bar in User, I'll need to do:

class User extends Foo[String] with Bar[String] {
  override def value: String = ???
  override def anotherValue: String = ???
}

Is there anyway that I can simplify User to this? (Bar automatically infers type from corresponding Foo.)

class User extends Foo[String] with Bar 

Solution

  • You can define intermediate trait

    trait UserLike[A] extends Foo[A] with Bar[A]
    
    class User extends UserLike[String] {
      override def value: String = ???
      override def anotherValue: String = ???
    }
    

    You can define a macro annotation but this would be an overkill

    @extendsBar
    class User extends Foo[String] {
      override def value: String = ???
      override def anotherValue: String = ???
    }
    
    //scalac: {
    //  class User extends Foo[String] with Bar[String] {
    //    def <init>() = {
    //      super.<init>();
    //      ()
    //    };
    //    override def value: String = $qmark$qmark$qmark;
    //    override def anotherValue: String = $qmark$qmark$qmark
    //  };
    //  ()
    //}
    
    import scala.annotation.StaticAnnotation
    import scala.language.experimental.macros
    import scala.reflect.macros.blackbox
    
    class extendsBar extends StaticAnnotation {
      def macroTransform(annottees: Any*): Any = macro ExtendsBarMacro.impl
    }
    
    object ExtendsBarMacro {
      def impl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
        import c.universe._
        annottees match {
          case q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" :: tail =>
            val fooArg = parents.collectFirst {
              case tq"Foo[$t]" => t
            }.getOrElse(c.abort(c.enclosingPosition, "class must extend Foo"))
            val parents1 = parents :+ tq"Bar[$fooArg]"
            q"""
               $mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents1 { $self => ..$stats }
               ..$tail
            """
          case _ =>
            c.abort(c.enclosingPosition, "annottee must be a class")
        }
      }
    }
    

    This would be more flexible if A were a type member rather than type parameter

    trait Foo {
      type A
      def value: A
    }
    
    trait Bar { self: Foo =>
      def anotherValue: A
    }
    
    class User extends Foo with Bar {
      override type A = String
      override def value: String = ???
      override def anotherValue: String = ???
    }