-
-
Save paulp/4692921 to your computer and use it in GitHub Desktop.
package s | |
object Test { | |
// Observe that x.companion is statically typed such that foo is callable | |
def f1() = { | |
val x = new Foo | |
println(x) // Foo instance | |
println(x.companion) // Foo companion | |
println(x.companion.foo) // I'm foo! | |
} | |
// Observe that we now have a static handle on the case class factory | |
def f2() = { | |
val y = Bippy(5) | |
println(y) // Bippy(5) | |
println(y.companion) // Bippy | |
println(y.companion.apply(10)) // Bippy(10) | |
} | |
def f3() = { | |
val y = new Bar | |
/**** | |
println(y.companion) // does not compile | |
b.scala:12: error: Instance of s.Bar has no companion object | |
println(y.companion) | |
^ | |
one error found | |
****/ | |
() | |
} | |
def main(args: Array[String]): Unit = { | |
f1() | |
f2() | |
} | |
} | |
class Foo { | |
override def toString = "Foo instance" | |
} | |
object Foo { | |
def foo = "I'm foo!" | |
override def toString = "Foo companion" | |
} | |
case class Bippy(x: Int) | |
class Bar { | |
override def toString = "Bar instance" | |
} | |
/** No object Bar **/ | |
import scala.language.experimental.macros | |
import scala.language.implicitConversions | |
import scala.reflect.macros.Context | |
package s { | |
object typeOps { | |
def companionImpl[T](c: Context) = { | |
import c.universe._ | |
val CompanionOpsClass = typeOf[CompanionOps[_]].typeSymbol | |
val clazzType = c.prefix.actualType match { | |
case TypeRef(_, CompanionOpsClass, arg :: Nil) => arg | |
case tp => c.abort(c.enclosingPosition, s"Unexpected prefix: $tp/${tp.getClass}") | |
} | |
val companion = clazzType.typeSymbol.companionSymbol match { | |
case NoSymbol => c.abort(c.enclosingPosition, s"Instance of $clazzType has no companion object") | |
case sym => sym | |
} | |
def make[U: c.WeakTypeTag] = c.Expr[U](treeBuild.mkAttributedRef(companion)) | |
make(c.WeakTypeTag(companion.typeSignature)) | |
} | |
} | |
} | |
package object s { | |
implicit class CompanionOps[T](val clazz: T) { | |
def companion: AnyRef = macro typeOps.companionImpl[T] | |
} | |
} |
well if you receive an instance of some type T that is statically known, you could then go from there to the companion. I can think of several patterns that have explicit companion references that this would make somewhat simpler
@rmellgren That's just the problem: if T is a type variable then you can't go to the companion in the way Paul is trying to do it. You could sprinkle implicit witnesses to enable that, but you could have done that anyway without the macro.
@milessabin So you would like to infer the type of the companion from the type of a case class instance? E.g. to put a constraint on which kind of case classes to accept based on the type of its companion.
Couldn't you let a macro generate an implicit witness to do that?
@jrudolph yes, something along those lines could be very useful.
I'm not really seeing the payoff here: if I statically know that x in f1 is of type Foo, then I can mention object Foo directly rather than having to go via x.companion. Is there anything I can do with .companion that I couldn't do already?