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

250 Views Asked by At

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 
1

There are 1 best solutions below

5
On BEST ANSWER

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 = ???
}