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

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

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

import scala.reflect.macros.blackbox
import com.softwaremill.macwire.internals._
import cats.implicits._
import scala.collection.immutable

trait GraphBuilderUtils[C <: blackbox.Context] { this: CatsProviders[C] =>
  val c: C
  val log: Logger

  /**
    * Lazy representation of FactoryMethod 
    * It's required because we may need to use a factory method to construct an intermediate instance in graph building process
    */
  case class FactoryMethodTree(params: List[c.universe.ValDef], fun: c.Tree, resultType: c.Type)
  object FactoryMethodTree {
    import c.universe._

    def apply(tree: Tree): Option[FactoryMethodTree] = FactoryMethod.deconstruct { case (fun, params) =>
      FactoryMethodTree(params, fun, FactoryMethod.underlyingType(fun))
    }(tree)
  }

  case class BuilderContext private (
      providers: List[Provider],
      notResolvedFactoryMethods: List[FactoryMethodTree]
  ) {
    import c.universe._

    def logContext = log(s"Available instances in context: [${providers.map(_.resultType)}] & [${notResolvedFactoryMethods.map(_.resultType)}]")
    def resolvedFactoryMethod(provider: FactoryMethod): BuilderContext = copy(
      providers = provider :: providers,
      notResolvedFactoryMethods = notResolvedFactoryMethods.filter(p => p.resultType != provider.resultType)
    )

    def resolve(tpe: Type): Option[Either[Provider, FactoryMethodTree]] = {
      logContext
      val resolvedProviders = providers
        .filter(_.resultType <:< tpe)
        .map(_.asLeft[FactoryMethodTree])

      val resolvedFMT = notResolvedFactoryMethods
        .filter(_.resultType <:< tpe)
        .map(_.asRight[Provider])

      val result = (resolvedProviders ++ resolvedFMT) match {
        case Nil          => None
        case List(result) => Some(result)
        case ambiguousProviders => {
          val duplicates = ambiguousProviders.map {
            case Left(value)  => value.resultType
            case Right(value) => value.resultType
          }.toSet

          c.abort(c.enclosingPosition, s"Ambiguous instances of types [${duplicates.mkString(", ")}]")
        }
      }

      log(s"For type [$tpe] found [$result]")

      result
    }

    def next(): Option[FactoryMethodTree] = {
      val value = notResolvedFactoryMethods.headOption
      log(s"Fetched next value [$value]")
      value
    }

    def addProvider(provider: Provider): BuilderContext =
      log.withBlock(s"For type [${provider.resultType}] add provider [${provider}]") {
        copy(
          providers = provider :: providers
        )
      }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy