com.softwaremill.macwire.internals.ConstructorCrimper.scala Maven / Gradle / Ivy
The newest version!
package com.softwaremill.macwire.internals
import scala.reflect.macros.blackbox
private[macwire] class ConstructorCrimper[C <: blackbox.Context, T: C#WeakTypeTag](val c: C, log: Logger) {
import c.universe._
type DependencyResolverType = DependencyResolver[c.type, Type, Tree]
lazy val targetType: Type = implicitly[c.WeakTypeTag[T]].tpe
// We need to get the "real" type in case the type parameter is a type alias - then it cannot
// be directly instantiated
lazy val targetTypeD: Type = targetType.dealias
lazy val constructor: Option[Symbol] = ConstructorCrimper.constructor(c, log)(targetType)
def constructorArgsWithImplicitLookups(dependencyResolver: DependencyResolverType): Option[List[List[Tree]]] =
log.withBlock("Looking for targetConstructor arguments with implicit lookups") {
constructor.map(_.asMethod.paramLists).map(wireConstructorParamsWithImplicitLookups(dependencyResolver))
}
def constructorTree(dependencyResolver: DependencyResolverType): Option[Tree] =
ConstructorCrimper.constructorTree(c, log)(targetType, dependencyResolver.resolve(_, _))
def wireConstructorParams(
dependencyResolver: DependencyResolverType
)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(
_.map(p => dependencyResolver.resolve(p, /*SI-4751*/ paramType(c)(targetTypeD, p)))
)
def wireConstructorParamsWithImplicitLookups(
dependencyResolver: DependencyResolverType
)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map {
case i if i.isImplicit => q"implicitly[${paramType(c)(targetType, i)}]"
case p => dependencyResolver.resolve(p, /*SI-4751*/ paramType(c)(targetTypeD, p))
})
}
object ConstructorCrimper {
def showConstructor[C <: blackbox.Context](c: C)(s: c.Symbol): String = s.asMethod.typeSignature.toString
private def constructor[C <: blackbox.Context](c: C, log: Logger)(targetType: c.Type) = {
import c.universe._
/** In some cases there is one extra (phantom) constructor. This happens when extended trait has implicit param:
*
* {{{
* trait A { implicit val a = ??? };
* class X extends A
* import scala.reflect.runtime.universe._
* typeOf[X].members.filter(m => m.isMethod && m.asMethod.isConstructor && m.asMethod.isPrimaryConstructor).map(_.asMethod.fullName)
*
* //res1: Iterable[String] = List(X., A.$init$)
* }}}
*
* The {{{A.$init$}}} is the phantom constructor and we don't want it.
*
* In other words, if we don't filter such constructor using this function
* 'wireActor-12-noPublicConstructor.failure' will compile and throw exception during runtime but we want to fail
* it during compilation time.
*/
def isPhantomConstructor(constructor: Symbol): Boolean = constructor.asMethod.fullName.endsWith("$init$")
lazy val publicConstructors: Iterable[Symbol] = {
val ctors = targetType.members
.filter(m => m.isMethod && m.asMethod.isConstructor && m.isPublic)
.filterNot(isPhantomConstructor)
log.withBlock(s"There are ${ctors.size} eligible constructors") { ctors.foreach(s => log(showConstructor(c)(s))) }
ctors
}
lazy val primaryConstructor: Option[Symbol] = publicConstructors.find(_.asMethod.isPrimaryConstructor)
lazy val injectConstructors: Iterable[Symbol] = {
val isInjectAnnotation = (a: Annotation) => a.toString == "javax.inject.Inject"
val ctors = publicConstructors.filter(_.annotations.exists(isInjectAnnotation))
log.withBlock(s"There are ${ctors.size} constructors annotated with @javax.inject.Inject") {
ctors.foreach(s => log(showConstructor(c)(s)))
}
ctors
}
lazy val injectConstructor: Option[Symbol] =
if (injectConstructors.size > 1)
c.abort(
c.enclosingPosition,
s"Ambiguous constructors annotated with @javax.inject.Inject for type [$targetType]"
)
else injectConstructors.headOption
log.withBlock(s"Looking for constructor for $targetType") {
val ctor = injectConstructor orElse primaryConstructor
ctor.foreach(ctor => log(s"Found ${showConstructor(c)(ctor)}"))
ctor
}
}
def constructorTree[C <: blackbox.Context](
c: C,
log: Logger
)(targetType: c.Type, resolver: (c.Symbol, c.Type) => c.Tree): Option[c.Tree] =
constructorFactory(c, log)(targetType).map { case (_, paramLists, factory) =>
import c.universe._
lazy val targetTypeD: Type = targetType.dealias
def wireConstructorParams(paramLists: List[List[Symbol]]): List[List[Tree]] =
paramLists.map(_.map(p => resolver(p, /*SI-4751*/ paramType(c)(targetTypeD, p))))
def constructorArgs: List[List[Tree]] = log.withBlock("Looking for targetConstructor arguments") {
wireConstructorParams(paramLists)
}
factory(constructorArgs)
}
def constructorFactory[C <: blackbox.Context](
c: C,
log: Logger
)(targetType: c.Type): Option[(c.Symbol, List[List[c.Symbol]], List[List[c.Tree]] => c.Tree)] = {
import c.universe._
lazy val targetTypeD: Type = targetType.dealias
val constructor: Option[Symbol] = ConstructorCrimper.constructor(c, log)(targetType)
val constructorParamLists: Option[List[List[Symbol]]] =
constructor.map(_.asMethod.paramLists.filterNot(_.headOption.exists(_.isImplicit)))
val constructionMethodTree: Tree = Select(New(Ident(targetTypeD.typeSymbol)), termNames.CONSTRUCTOR)
def factory(constructorArgs: List[List[Tree]]): Tree = {
constructorArgs.foldLeft(constructionMethodTree)((acc: Tree, args: List[Tree]) => Apply(acc, args))
}
for {
cpl <- constructorParamLists
c <- constructor
} yield (c, cpl, factory(_))
}
}