I'd like to have a base case class I can extend at will into different types. I had something like this working previously, but I must have screwed something up, because now I'm getting compile errors.
trait JobLike
case class Task(name: String) {
def as[T <: JobLike]: Task with T = new Task(this.name) with T
}
The problem is, this gives me a compiler error like this:
java.lang.UnsupportedOperationException: addChild inapplicable for type T
at scala.reflect.internal.Symbols$Symbol.addChild(Symbols.scala:1835)
at scala.tools.nsc.typechecker.Namers$Namer.$anonfun$templateSig$1(Namers.scala:1119)
at scala.tools.nsc.typechecker.Namers$Namer.templateSig(Namers.scala:1107)
at scala.tools.nsc.typechecker.Namers$Namer.classSig(Namers.scala:1178)
at scala.tools.nsc.typechecker.Namers$Namer.memberSig(Namers.scala:1788)
at scala.tools.nsc.typechecker.Namers$Namer.typeSig(Namers.scala:1751)
at scala.tools.nsc.typechecker.Namers$Namer$MonoTypeCompleter.completeImpl(Namers.scala:836)
...
at scala.tools.nsc.MainGenericRunner.runTarget$1(MainGenericRunner.scala:82)
at scala.tools.nsc.MainGenericRunner.run$1(MainGenericRunner.scala:85)
at scala.tools.nsc.MainGenericRunner.process(MainGenericRunner.scala:96)
at scala.tools.nsc.MainGenericRunner$.main(MainGenericRunner.scala:101)
at scala.tools.nsc.MainGenericRunner.main(MainGenericRunner.scala)</code>
I swear, I had something working yesterday that basically did just this, but now it's throwing this error.
You managed to hit a compiler bug! While that's certainly an issue, and should be reported, the thing is that your code isn't possible anyway, and it's better that it's crashing and bringing you here than "working" and doing something horribly wrong. Because
T
can be any subtype ofJobLike
, there's simply no way to mix it in at compile time. I could very well dotrait Boom extends JobLike { def something: Int }; task.as[Boom]
, and thentask
couldn't create aBoom
because there's no implementation forsomething
.You can provide
asJobLike: Task with JobLike
instead of a generalas[T]: Task with T
, which is completely possible. (Note that it's discouraged to make subtypes ofcase class
es, so you may want to create a new class. The reason is that it breaks the reflexivity ofequals
.)Something rather filthy that you can do is call
(new Task(this.name) with JobLike).asInstanceOf[Task with T]
.asInstanceOf
only checks the cast for the leftmost type in thewith
, so this will notClassCastException
off the bat (you can also do"a".asInstanceOf[String with Int]
.). It will generally sorta work (modulusasInstanceOf[T]
beingfalse
) as long asT
has no members.Something horrible that you can also do is use runtime reflection to dynamically create the required class. The trick as a whole is filthy and completely unsafe, so I won't say it here, but have a link.