All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.softwaremill.macwire.autocats.internals.CatsProviders.scala Maven / Gradle / Ivy

The newest version!
package com.softwaremill.macwire.autocats.internals

import scala.reflect.macros.blackbox
import com.softwaremill.macwire.internals._

trait CatsProviders[C <: blackbox.Context] {
  val c: C
  val log: Logger

  val typeCheckUtil: TypeCheckUtil[c.type]

  sealed trait Provider {
    def resultType: c.Type
    def dependencies: List[List[(c.Symbol, Provider)]]
    def ident: c.Tree
    def value: c.Tree
    def symbol: c.Symbol
  }

  class Effect(rawValue: c.Tree) extends Provider {

    override def symbol: c.Symbol = rawValue.symbol

    import c.universe._

    lazy val resultType: Type = Effect.underlyingType(typeCheckUtil.typeCheckIfNeeded(rawValue))
    lazy val dependencies: List[List[(c.Symbol, Provider)]] = List.empty
    lazy val asResource = new Resource(q"cats.effect.kernel.Resource.eval[cats.effect.IO, ${resultType}]($rawValue)")
    lazy val value: Tree = asResource.value
    lazy val ident: Tree = asResource.ident
  }

  object Effect {
    import c.universe._

    def underlyingType(tpe: Type): Type = tpe.typeArgs(0)
    def maybeUnderlyingType(tpe: Type): Option[Type] = if (isEffect(tpe)) Some(underlyingType(tpe)) else None
    def isEffect(tpe: Type): Boolean = tpe.typeSymbol.fullName.startsWith("cats.effect.IO") && tpe.typeArgs.size == 1
    def fromTree(tree: Tree, tpe: Type): Option[Effect] =
      if (isEffect(tpe)) Some(new Effect(tree))
      else None

  }

  class Resource(val value: c.Tree) extends Provider {

    override def symbol: c.Symbol = value.symbol

    import c.universe._

    lazy val resultType: Type = Resource.underlyingType(typeCheckUtil.typeCheckIfNeeded(value))
    lazy val dependencies: List[List[(c.Symbol, Provider)]] = List.empty
    lazy val ident: Tree = Ident(TermName(c.freshName()))
  }

  object Resource {
    import c.universe._

    def underlyingType(tpe: Type): Type = tpe.typeArgs(1)
    def maybeUnderlyingType(tpe: Type): Option[Type] = if (isResource(tpe)) Some(underlyingType(tpe)) else None

    def isResource(tpe: Type): Boolean =
      tpe.typeSymbol.fullName.startsWith("cats.effect.kernel.Resource") && tpe.typeArgs.size == 2

    def fromTree(tree: Tree, tpe: Type): Option[Resource] =
      if (isResource(tpe)) Some(new Resource(tree))
      else None

  }

  class FactoryMethod(
    val symbol: c.Symbol,
      methodType: c.Type,
      val resultType: c.Type,
      val dependencies: List[List[(c.Symbol, Provider)]],
      apply: List[List[c.Tree]] => c.Tree
  ) extends Provider {
    import c.universe._

    private lazy val appliedTree: Tree = apply(dependencies.map(_.map(_._2.ident)))
    
    lazy val result: Provider = log.withResult {
      val fmResultType = resultType
      //TODO support for FactoryMethods
      if (Resource.isResource(methodType)) new Resource(appliedTree) {
        override lazy val resultType: Type = Resource.underlyingType(methodType)
      }
      else if (Effect.isEffect(methodType)) new Effect(appliedTree) {
        override lazy val resultType: Type = Effect.underlyingType(methodType)
      }
      else
        new Resource(q"cats.effect.Resource.pure[cats.effect.IO, $resultType]($appliedTree)") {
          override lazy val resultType: Type = fmResultType
        }
    }(result => s"Factory method result [$result]")

    lazy val ident: Tree = result.ident

    lazy val value: Tree = result.value

  }

  object FactoryMethod {
    import c.universe._

    def underlyingType(tree: Tree): Type = {
      val resultType = tree.symbol.asMethod.returnType
      (Resource.maybeUnderlyingType(resultType) orElse Effect.maybeUnderlyingType(resultType)).getOrElse(resultType)
    }

    def underlyingResultType(tree: Tree): Type = {
      val (fun, _) =
        fromTree(tree).getOrElse(c.abort(c.enclosingPosition, s"The given tree is not a factory method: [$tree]"))
      underlyingType(fun)
    }

    def unapply(tree: Tree): Option[(Tree, List[ValDef])] = deconstruct(identity)(tree)
    def fromTree(tree: Tree): Option[(Tree, List[ValDef])] = unapply(tree)
    def isFactoryMethod(tree: Tree): Boolean = fromTree(tree).isDefined

    def deconstruct[T](componentsTransformer: ((Tree, List[ValDef])) => T)(tree: Tree): Option[T] = tree match {
      // Function with two parameter lists (implicit parameters) (<2.13)
      case Block(Nil, Function(p, Apply(Apply(f, _), _))) => Some(componentsTransformer((f, p)))
      case Block(Nil, Function(p, Apply(f, _)))           => Some(componentsTransformer((f, p)))
      // Function with two parameter lists (implicit parameters) (>=2.13)
      case Function(p, Apply(Apply(f, _), _)) => Some(componentsTransformer((f, p)))
      case Function(p, Apply(f, _))           => Some(componentsTransformer((f, p)))
      // Other types not supported
      case _ => None
    }

  }

  class Instance(val value: c.Tree) extends Provider {

    override def symbol: c.Symbol = value.symbol


    override def dependencies: List[List[(c.Symbol, Provider)]] = List.empty

    lazy val resultType: c.Type = typeCheckUtil.typeCheckIfNeeded(value)
    lazy val ident: c.Tree = value

  }

  class NotResolvedProvider(val resultType: c.Type, val symbol: c.Symbol) extends Provider {
    override def dependencies: List[List[(c.Symbol, Provider)]] = c.abort(c.enclosingPosition, s"Internal Error: Not resolved provider for type [$resultType]")
    override def ident: c.Tree = c.abort(c.enclosingPosition, s"Internal Error: Not resolved provider for type [$resultType]")
    override def value: c.Tree = c.abort(c.enclosingPosition, s"Internal Error: Not resolved provider for type [$resultType]")


  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy