From 45c1e6357ff488086074a4c5a719f4c84986c69e Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Mon, 1 Nov 2021 14:19:59 +0100 Subject: [PATCH 1/8] Add support for resource factory methods --- .../autocats/MacwireAutoCatsMacros.scala | 277 +++++++++++++----- .../{todo => }/chainingFactoryMethods.success | 2 +- .../test-cases/factoryWithParameter.success | 3 +- .../factoryWithParameterFromIO.success | 0 .../factoryWithParameterFromResource.success | 0 5 files changed, 206 insertions(+), 76 deletions(-) rename macrosAutoCatsTests/src/test/resources/test-cases/{todo => }/chainingFactoryMethods.success (94%) rename macrosAutoCatsTests/src/test/resources/test-cases/{todo => }/factoryWithParameterFromIO.success (100%) rename macrosAutoCatsTests/src/test/resources/test-cases/{todo => }/factoryWithParameterFromResource.success (100%) diff --git a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala index 20285e0c..46999ccd 100644 --- a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala @@ -17,107 +17,244 @@ object MacwireCatsEffectMacros { val targetType = implicitly[c.WeakTypeTag[T]] lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) - trait Provider { + sealed trait Provider { def `type`: Type } - case class Resource(value: Tree) extends Provider { - val `type`: Type = typeCheckUtil.typeCheckIfNeeded(value).typeArgs(1) - val ident: Tree = Ident(TermName(c.freshName())) - lazy val tpe = typeCheckUtil.typeCheckIfNeeded(value) + sealed trait Value extends Provider { + def ident: Tree } - case class Instance(value: Tree) extends Provider { + class Resource(valueL: => Tree) extends Value { + lazy val value = valueL + + lazy val `type`: Type = Resource.underlyingType(typeCheckUtil.typeCheckIfNeeded(value)).getOrElse(c.abort(c.enclosingPosition, "TODO")) + lazy val ident: Tree = Ident(TermName(c.freshName())) + // lazy val tpe = typeCheckUtil.typeCheckIfNeeded(value) + + } + + object Resource { + def fromTree(tree: Tree): Option[Resource] = + if (isResource(typeCheckUtil.typeCheckIfNeeded(tree))) Some(new Resource(tree)) + else None + + def underlyingType(tpe: Type): Option[Type] = if(isResource(tpe)) Some(tpe.typeArgs(1)) else None + + def isResource(tpe: Type): Boolean = + tpe.typeSymbol.fullName.startsWith("cats.effect.kernel.Resource") && tpe.typeArgs.size == 2 + + } + + case class Instance(value: Tree) extends Value { lazy val `type`: Type = typeCheckUtil.typeCheckIfNeeded(value) lazy val ident: Tree = value } - case class FactoryMethod(value: Tree) extends Provider { - val (params, fun) = value match { + class FactoryMethod(params: List[ValDef], fun: Tree) extends Provider { + + def result(resolver: Type => Tree): Value = { + + val resultTree = applyWith(resolver) + +val t = fun.symbol.asMethod.returnType + if (Resource.isResource(t)) new Resource(resultTree) { + override lazy val `type`: Type = tt + } + else if (Effect.isEffect(t)) new Effect(resultTree) { + override lazy val `type`: Type = tt + } + else Instance(resultTree) + + } + + lazy val `type`: Type = { + val resultType = fun.symbol.asMethod.returnType + (Resource.underlyingType(resultType) orElse Effect.underlyingType(resultType)).getOrElse(resultType) + } +lazy val tt = `type` + def applyWith(resolver: Type => Tree): Tree = { + val values = params.map { case ValDef(_, name, tpt, rhs) => + resolver(typeCheckUtil.typeCheckIfNeeded(tpt)) + } + + q"$fun(..$values)" + } + + } + + object FactoryMethod { + def fromTree(tree: Tree): Option[FactoryMethod] = tree match { // Function with two parameter lists (implicit parameters) (<2.13) - case Block(Nil, Function(p, Apply(Apply(f, _), _))) => (p, f) - case Block(Nil, Function(p, Apply(f, _))) => (p, f) + case Block(Nil, Function(p, Apply(Apply(f, _), _))) => Some(new FactoryMethod(p, f)) + case Block(Nil, Function(p, Apply(f, _))) => Some(new FactoryMethod(p, f)) // Function with two parameter lists (implicit parameters) (>=2.13) - case Function(p, Apply(Apply(f, _), _)) => (p, f) - case Function(p, Apply(f, _)) => (p, f) + case Function(p, Apply(Apply(f, _), _)) => Some(new FactoryMethod(p, f)) + case Function(p, Apply(f, _)) => Some(new FactoryMethod(p, f)) // Other types not supported - case _ => c.abort(c.enclosingPosition, s"Not supported factory type: [$value]") + case _ => None } - lazy val `type`: Type = fun.symbol.asMethod.returnType + } - def applyWith(resolver: Resolver): Tree = { - val values = params.map { case vd @ ValDef(_, name, tpt, rhs) => - resolver(vd.symbol, typeCheckUtil.typeCheckIfNeeded(tpt)) - } + class Effect(value: Tree) extends Value { - q"$fun(..$values)" + override def ident: Tree = asResource.ident//???? + + override lazy val `type`: Type = typeCheckUtil.typeCheckIfNeeded(value).typeArgs(0) +def ttt = `type` + lazy val asResource = new Resource(q"cats.effect.kernel.Resource.eval[cats.effect.IO, ${`type`}]($value)") { + override lazy val `type`: Type = ttt } + } + + object Effect { + def fromTree(tree: Tree): Option[Effect] = + if (isEffect(typeCheckUtil.typeCheckIfNeeded(tree))) Some(new Effect(tree)) + else None + + def underlyingType(tpe: Type): Option[Type] = if(isEffect(tpe)) Some(tpe.typeArgs(0)) else None + def isEffect(tpe: Type): Boolean = + tpe.typeSymbol.fullName.startsWith("cats.effect.IO") && tpe.typeArgs.size == 1 + + } + + def providerFromExpr(expr: Expr[Any]): Provider = { + val tree = expr.tree + (Resource.fromTree(tree) orElse Effect.fromTree(tree) orElse FactoryMethod.fromTree(tree)) + .getOrElse(new Instance(tree)) } - case class Effect(value: Tree) extends Provider { + def findProviderIn(values: Seq[Value])(tpe: Type): Option[Tree] = values.find(_.`type` <:< tpe).map(_.ident) - override val `type`: Type = typeCheckUtil.typeCheckIfNeeded(value).typeArgs(0) + val providers = dependencies.map(providerFromExpr) + val values: Seq[Value] = { + val init: Seq[Value] = providers.flatMap { + case e: Effect => Some(e.asResource) + case v: Value => Some(v) + case _ => None + } - lazy val asResource = Resource(q"cats.effect.kernel.Resource.eval[cats.effect.IO, ${`type`}]($value)") + providers.foldLeft(init) { + case (v, fm: FactoryMethod) => { + v :+ fm.result(findProviderIn(v)(_).getOrElse(c.abort(c.enclosingPosition, "TODO2"))) + } + case (v, _ ) => v + }.collect { + case v: Value => v + } } - def isResource(expr: Expr[Any]): Boolean = { - val checkedType = typeCheckUtil.typeCheckIfNeeded(expr.tree) + log(s"exprs: s[${dependencies.mkString(", ")}]") + log(s"Providers: [${providers.mkString(", ")}]") + log(s"Values: [${values.mkString(", ")}]") - checkedType.typeSymbol.fullName.startsWith("cats.effect.kernel.Resource") && checkedType.typeArgs.size == 2 + def findProvider(tpe: Type): Option[Tree] = findProviderIn(values)(tpe) + + def isWireable(tpe: Type): Boolean = { + val name = tpe.typeSymbol.fullName + + !name.startsWith("java.lang.") && !name.startsWith("scala.") } - def isFactoryMethod(expr: Expr[Any]): Boolean = expr.tree match { - // Function with two parameter lists (implicit parameters) (<2.13) - case Block(Nil, Function(p, Apply(Apply(f, _), _))) => true - case Block(Nil, Function(p, Apply(f, _))) => true - // Function with two parameter lists (implicit parameters) (>=2.13) - case Function(p, Apply(Apply(f, _), _)) => true - case Function(p, Apply(f, _)) => true - // Other types not supported - case _ => false + lazy val resolutionWithFallback: (Symbol, Type) => Tree = (_, tpe) => + if (isWireable(tpe)) findProvider(tpe).getOrElse(go(tpe)) + else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") + + def go(t: Type): Tree = { + + val r = + (ConstructorCrimper.constructorTree(c, log)(t, resolutionWithFallback) orElse CompanionCrimper + .applyTree(c, log)(t, resolutionWithFallback)) getOrElse + c.abort(c.enclosingPosition, s"Failed for [$t]") + + log(s"Constructed [$r]") + r + } + + val resources: Seq[Resource] = values.collect { + case r: Resource => r + case e: Effect => e.asResource } - def isEffect(expr: Expr[Any]): Boolean = { - val checkedType = typeCheckUtil.typeCheckIfNeeded(expr.tree) + val code = resources.foldRight(q"cats.effect.Resource.pure[cats.effect.IO, $targetType](autowirePure[$targetType](..${values.map(_.ident)}))") { + case (resource, acc) => + q"${resource.value}.flatMap((${resource.ident}: ${resource.`type`}) => $acc)" + } + log(s"Code: [$code]") - checkedType.typeSymbol.fullName.startsWith("cats.effect.IO") && checkedType.typeArgs.size == 1 + c.Expr[CatsResource[IO, T]](code) + } + + def autowirePure_impl[T: c.WeakTypeTag]( + c: blackbox.Context + )(dependencies: c.Expr[Any]*): c.Expr[T] = { + import c.universe._ + + type Resolver = (Symbol, Type) => Tree + + val targetType = implicitly[c.WeakTypeTag[T]] + lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) + + sealed trait Provider { + def `type`: Type } - val resourcesExprs = dependencies.filter(isResource) - val factoryMethodsExprs = dependencies.filter(isFactoryMethod) - val effectsExprs = dependencies.filter(isEffect) - val instancesExprs = dependencies.diff(resourcesExprs).diff(factoryMethodsExprs).diff(effectsExprs) + case class Instance(value: Tree) extends Provider { + lazy val `type`: Type = typeCheckUtil.typeCheckIfNeeded(value) + lazy val ident: Tree = value + } - val resources = - (effectsExprs.map(expr => Effect(expr.tree).asResource) ++ resourcesExprs.map(expr => Resource(expr.tree))) - .map(r => (r.`type`, r)) - .toMap + class FactoryMethod(params: List[ValDef], fun: Tree) extends Provider { - val instances = instancesExprs - .map(expr => Instance(expr.tree)) - .map(i => (i.`type`, i)) - .toMap + def result(resolver: Type => Tree): Instance = new Instance(applyWith(resolver)) - val factoryMethods = factoryMethodsExprs - .map(expr => FactoryMethod(expr.tree)) - .map(i => (i.`type`, i)) - .toMap - log(s"exprs: s[${dependencies.mkString(", ")}]") - log(s"resources: [${resources.mkString(", ")}]") - log(s"instances: [${instances.mkString(", ")}]") - log(s"factory methods: [${factoryMethods.mkString(", ")}]") - def doFind[T <: Provider](values: Map[Type, T])(tpe: Type): Option[T] = - values.find { case (t, _) => t <:< tpe }.map(_._2) + lazy val `type`: Type = fun.symbol.asMethod.returnType + + def applyWith(resolver: Type => Tree): Tree = { + val values = params.map { case ValDef(_, name, tpt, rhs) => + resolver(typeCheckUtil.typeCheckIfNeeded(tpt)) + } + + q"$fun(..$values)" + } + + } + + object FactoryMethod { + def fromTree(tree: Tree): Option[FactoryMethod] = tree match { + // Function with two parameter lists (implicit parameters) (<2.13) + case Block(Nil, Function(p, Apply(Apply(f, _), _))) => Some(new FactoryMethod(p, f)) + case Block(Nil, Function(p, Apply(f, _))) => Some(new FactoryMethod(p, f)) + // Function with two parameter lists (implicit parameters) (>=2.13) + case Function(p, Apply(Apply(f, _), _)) => Some(new FactoryMethod(p, f)) + case Function(p, Apply(f, _)) => Some(new FactoryMethod(p, f)) + // Other types not supported + case _ => None + } - def findInstance(t: Type): Option[Instance] = doFind(instances)(t) + } + + def providerFromExpr(expr: Expr[Any]): Provider = { + val tree = expr.tree + FactoryMethod.fromTree(tree) + .getOrElse(new Instance(tree)) + } + + val providers = dependencies.map(providerFromExpr) - def findResource(t: Type): Option[Resource] = doFind(resources)(t) - def findFactoryMethod(t: Type): Option[FactoryMethod] = doFind(factoryMethods)(t) + + log(s"PURE exprs: s[${dependencies.mkString(", ")}]") + log(s"PURE Providers: [${providers.mkString(", ")}]") + log(s"PURE ProvidersTYPES: [${providers.map(_.`type`).mkString(", ")}]") + + def findProvider(tpe: Type): Option[Tree] = providers.find(_.`type` <:< tpe).map { + case i: Instance => i.ident + case fm: FactoryMethod => fm.result(findProvider(_).getOrElse(c.abort(c.enclosingPosition, "TODO3"))).ident + } def isWireable(tpe: Type): Boolean = { val name = tpe.typeSymbol.fullName @@ -125,11 +262,6 @@ object MacwireCatsEffectMacros { !name.startsWith("java.lang.") && !name.startsWith("scala.") } - def findProvider(tpe: Type): Option[Tree] = findInstance(tpe) - .map(_.ident) - .orElse(findResource(tpe).map(_.ident)) - .orElse(findFactoryMethod(tpe).map(_.applyWith(resolutionWithFallback))) - lazy val resolutionWithFallback: (Symbol, Type) => Tree = (_, tpe) => if (isWireable(tpe)) findProvider(tpe).getOrElse(go(tpe)) else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") @@ -145,16 +277,13 @@ object MacwireCatsEffectMacros { r } - val generatedInstance = go(targetType.tpe) - val code = - resources.values.foldRight(q"cats.effect.Resource.pure[cats.effect.IO, $targetType]($generatedInstance)") { - case (resource, acc) => - q"${resource.value}.flatMap((${resource.ident}: ${resource.tpe}) => $acc)" - } + val code = go(targetType.tpe) log(s"Code: [$code]") - c.Expr[CatsResource[IO, T]](code) + c.Expr[T](code) } + + } diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/todo/chainingFactoryMethods.success b/macrosAutoCatsTests/src/test/resources/test-cases/chainingFactoryMethods.success similarity index 94% rename from macrosAutoCatsTests/src/test/resources/test-cases/todo/chainingFactoryMethods.success rename to macrosAutoCatsTests/src/test/resources/test-cases/chainingFactoryMethods.success index a7e1003a..a3cfa588 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/todo/chainingFactoryMethods.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/chainingFactoryMethods.success @@ -22,4 +22,4 @@ require(theC.b != null) require(created.size == 2) require(created.contains("a")) -require(created.contains("B")) +require(created.contains("b")) diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameter.success index 688d08c7..34fbc874 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameter.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameter.success @@ -6,8 +6,9 @@ import cats.effect._ val created = scala.collection.mutable.Set[String]() object Test { + val theB: B = B() def theA(b: B): A = { created.add(s"A using $b"); A() } - val theC: Resource[IO, C] = autowire[C](theA _) + val theC: Resource[IO, C] = autowire[C](theB, theA _) } val theC: C = { diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromIO.success b/macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameterFromIO.success similarity index 100% rename from macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromIO.success rename to macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameterFromIO.success diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success b/macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameterFromResource.success similarity index 100% rename from macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success rename to macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameterFromResource.success From 34cfb788bddb02c4eeb29f012d037c5c76a7f460 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Mon, 1 Nov 2021 14:41:03 +0100 Subject: [PATCH 2/8] add pure autowire --- .../softwaremill/macwire/MacwireMacros.scala | 92 +++++++++++++++ .../com/softwaremill/macwire/package.scala | 2 + .../autocats/MacwireAutoCatsMacros.scala | 110 +----------------- .../macwire/autocats/package.scala | 2 +- .../macwire/BaseCompileTestSupport.scala | 6 +- 5 files changed, 104 insertions(+), 108 deletions(-) diff --git a/macros/src/main/scala-2/com/softwaremill/macwire/MacwireMacros.scala b/macros/src/main/scala-2/com/softwaremill/macwire/MacwireMacros.scala index 787b4420..5795b8f9 100644 --- a/macros/src/main/scala-2/com/softwaremill/macwire/MacwireMacros.scala +++ b/macros/src/main/scala-2/com/softwaremill/macwire/MacwireMacros.scala @@ -149,4 +149,96 @@ object MacwireMacros { } } + def autowire_impl[T: c.WeakTypeTag]( + c: blackbox.Context + )(dependencies: c.Expr[Any]*): c.Expr[T] = { + import c.universe._ + + val targetType = implicitly[c.WeakTypeTag[T]] + lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) + + sealed trait Provider { + def `type`: Type + } + + case class Instance(value: Tree) extends Provider { + lazy val `type`: Type = typeCheckUtil.typeCheckIfNeeded(value) + lazy val ident: Tree = value + } + + class FactoryMethod(params: List[ValDef], fun: Tree) extends Provider { + + def result(resolver: Type => Tree): Instance = new Instance(applyWith(resolver)) + + lazy val `type`: Type = fun.symbol.asMethod.returnType + + def applyWith(resolver: Type => Tree): Tree = { + val values = params.map { case ValDef(_, name, tpt, rhs) => + resolver(typeCheckUtil.typeCheckIfNeeded(tpt)) + } + + q"$fun(..$values)" + } + + } + + object FactoryMethod { + def fromTree(tree: Tree): Option[FactoryMethod] = tree match { + // Function with two parameter lists (implicit parameters) (<2.13) + case Block(Nil, Function(p, Apply(Apply(f, _), _))) => Some(new FactoryMethod(p, f)) + case Block(Nil, Function(p, Apply(f, _))) => Some(new FactoryMethod(p, f)) + // Function with two parameter lists (implicit parameters) (>=2.13) + case Function(p, Apply(Apply(f, _), _)) => Some(new FactoryMethod(p, f)) + case Function(p, Apply(f, _)) => Some(new FactoryMethod(p, f)) + // Other types not supported + case _ => None + } + + } + + def providerFromExpr(expr: Expr[Any]): Provider = { + val tree = expr.tree + FactoryMethod.fromTree(tree) + .getOrElse(new Instance(tree)) + } + + val providers = dependencies.map(providerFromExpr) + + log(s"PURE exprs: s[${dependencies.mkString(", ")}]") + log(s"PURE Providers: [${providers.mkString(", ")}]") + log(s"PURE ProvidersTYPES: [${providers.map(_.`type`).mkString(", ")}]") + + def findProvider(tpe: Type): Option[Tree] = providers.find(_.`type` <:< tpe).map { + case i: Instance => i.ident + case fm: FactoryMethod => fm.result(findProvider(_).getOrElse(c.abort(c.enclosingPosition, "TODO3"))).ident + } + + def isWireable(tpe: Type): Boolean = { + val name = tpe.typeSymbol.fullName + + !name.startsWith("java.lang.") && !name.startsWith("scala.") + } + + lazy val resolutionWithFallback: (Symbol, Type) => Tree = (_, tpe) => + if (isWireable(tpe)) findProvider(tpe).getOrElse(go(tpe)) + else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") + + def go(t: Type): Tree = { + + val r = + (ConstructorCrimper.constructorTree(c, log)(t, resolutionWithFallback) orElse CompanionCrimper + .applyTree(c, log)(t, resolutionWithFallback)) getOrElse + c.abort(c.enclosingPosition, s"Failed for [$t]") + + log(s"Constructed [$r]") + r + } + + + val code = go(targetType.tpe) + log(s"Code: [$code]") + + c.Expr[T](code) + } + } diff --git a/macros/src/main/scala-2/com/softwaremill/macwire/package.scala b/macros/src/main/scala-2/com/softwaremill/macwire/package.scala index d3f73f25..60457973 100644 --- a/macros/src/main/scala-2/com/softwaremill/macwire/package.scala +++ b/macros/src/main/scala-2/com/softwaremill/macwire/package.scala @@ -61,4 +61,6 @@ package object macwire { def wireRec[T]: T = macro MacwireMacros.wireRec_impl[T] + def autowire[T](dependencies: Any*): T = macro MacwireMacros.autowire_impl[T] + } diff --git a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala index 46999ccd..13a599d3 100644 --- a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala @@ -102,7 +102,8 @@ lazy val tt = `type` override def ident: Tree = asResource.ident//???? override lazy val `type`: Type = typeCheckUtil.typeCheckIfNeeded(value).typeArgs(0) -def ttt = `type` + + def ttt = `type` lazy val asResource = new Resource(q"cats.effect.kernel.Resource.eval[cats.effect.IO, ${`type`}]($value)") { override lazy val `type`: Type = ttt } @@ -178,112 +179,13 @@ def ttt = `type` case e: Effect => e.asResource } - val code = resources.foldRight(q"cats.effect.Resource.pure[cats.effect.IO, $targetType](autowirePure[$targetType](..${values.map(_.ident)}))") { - case (resource, acc) => - q"${resource.value}.flatMap((${resource.ident}: ${resource.`type`}) => $acc)" - } - log(s"Code: [$code]") - - c.Expr[CatsResource[IO, T]](code) - } - - def autowirePure_impl[T: c.WeakTypeTag]( - c: blackbox.Context - )(dependencies: c.Expr[Any]*): c.Expr[T] = { - import c.universe._ - - type Resolver = (Symbol, Type) => Tree - - val targetType = implicitly[c.WeakTypeTag[T]] - lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) - - sealed trait Provider { - def `type`: Type - } - - case class Instance(value: Tree) extends Provider { - lazy val `type`: Type = typeCheckUtil.typeCheckIfNeeded(value) - lazy val ident: Tree = value - } - - class FactoryMethod(params: List[ValDef], fun: Tree) extends Provider { - - def result(resolver: Type => Tree): Instance = new Instance(applyWith(resolver)) - - - - lazy val `type`: Type = fun.symbol.asMethod.returnType - - def applyWith(resolver: Type => Tree): Tree = { - val values = params.map { case ValDef(_, name, tpt, rhs) => - resolver(typeCheckUtil.typeCheckIfNeeded(tpt)) - } - - q"$fun(..$values)" - } - - } - - object FactoryMethod { - def fromTree(tree: Tree): Option[FactoryMethod] = tree match { - // Function with two parameter lists (implicit parameters) (<2.13) - case Block(Nil, Function(p, Apply(Apply(f, _), _))) => Some(new FactoryMethod(p, f)) - case Block(Nil, Function(p, Apply(f, _))) => Some(new FactoryMethod(p, f)) - // Function with two parameter lists (implicit parameters) (>=2.13) - case Function(p, Apply(Apply(f, _), _)) => Some(new FactoryMethod(p, f)) - case Function(p, Apply(f, _)) => Some(new FactoryMethod(p, f)) - // Other types not supported - case _ => None - } - - } - - def providerFromExpr(expr: Expr[Any]): Provider = { - val tree = expr.tree - FactoryMethod.fromTree(tree) - .getOrElse(new Instance(tree)) - } - - val providers = dependencies.map(providerFromExpr) - - - log(s"PURE exprs: s[${dependencies.mkString(", ")}]") - log(s"PURE Providers: [${providers.mkString(", ")}]") - log(s"PURE ProvidersTYPES: [${providers.map(_.`type`).mkString(", ")}]") - - def findProvider(tpe: Type): Option[Tree] = providers.find(_.`type` <:< tpe).map { - case i: Instance => i.ident - case fm: FactoryMethod => fm.result(findProvider(_).getOrElse(c.abort(c.enclosingPosition, "TODO3"))).ident - } - - def isWireable(tpe: Type): Boolean = { - val name = tpe.typeSymbol.fullName - - !name.startsWith("java.lang.") && !name.startsWith("scala.") - } - - lazy val resolutionWithFallback: (Symbol, Type) => Tree = (_, tpe) => - if (isWireable(tpe)) findProvider(tpe).getOrElse(go(tpe)) - else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") - - def go(t: Type): Tree = { - - val r = - (ConstructorCrimper.constructorTree(c, log)(t, resolutionWithFallback) orElse CompanionCrimper - .applyTree(c, log)(t, resolutionWithFallback)) getOrElse - c.abort(c.enclosingPosition, s"Failed for [$t]") - - log(s"Constructed [$r]") - r + val code = resources.foldRight(q"cats.effect.Resource.pure[cats.effect.IO, $targetType](com.softwaremill.macwire.autowire[$targetType](..${values.map(_.ident)}))") { + case (resource, acc) => + q"${resource.value}.flatMap((${resource.ident}: ${resource.`type`}) => $acc)" } - - - val code = go(targetType.tpe) log(s"Code: [$code]") - c.Expr[T](code) + c.Expr[CatsResource[IO, T]](code) } - - } diff --git a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/package.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/package.scala index 8ac5fb28..e2358b17 100644 --- a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/package.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/package.scala @@ -3,5 +3,5 @@ package com.softwaremill.macwire import cats.effect._ package object autocats { - def autowire[T](dependencies: Any*): Resource[IO, T] = macro MacwireCatsEffectMacros.autowire_impl[T] + def autowire[T](dependencies: Any*): Resource[IO, T] = macro MacwireCatsEffectMacros.autowire_impl[T] } diff --git a/test-util/src/main/scala/com/softwaremill/macwire/BaseCompileTestSupport.scala b/test-util/src/main/scala/com/softwaremill/macwire/BaseCompileTestSupport.scala index b381af3f..fa48dccb 100644 --- a/test-util/src/main/scala/com/softwaremill/macwire/BaseCompileTestSupport.scala +++ b/test-util/src/main/scala/com/softwaremill/macwire/BaseCompileTestSupport.scala @@ -10,9 +10,9 @@ import scala.io.Source private[macwire] trait BaseCompileTestsSupport extends AnyFlatSpec with Matchers { type ExpectedFailures = List[String] - val GlobalImports = "import com.softwaremill.macwire._\n\n" - val DirectiveRegexp = "#include ([a-zA-Z]+)".r - val EmptyResult = "\n\n()" + val GlobalImports = "import com.softwaremill.macwire.{wire, wireWith, wireSet, wireRec}\n\n" + val DirectiveRegexp = "#include ([a-zA-Z]+)".r + val EmptyResult = "\n\n()" def ambiguousResMsg(depClassName: String): String = s"Found multiple values of type [$depClassName]" def valueNotFound(depClassName: String): String = s"Cannot find a value of type: [$depClassName]" From 0f9386709e27473c1ab5723817a7159e2e83daf4 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Wed, 3 Nov 2021 19:04:26 +0100 Subject: [PATCH 3/8] Test allocation order --- .../test-cases/chainingFactoryMethods.success | 10 +++++----- .../resources/test-cases/ioProvidedParameter.success | 10 +++++----- .../test-cases/resourceProvidedParameters.success | 10 +++++----- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/chainingFactoryMethods.success b/macrosAutoCatsTests/src/test/resources/test-cases/chainingFactoryMethods.success index a3cfa588..d4011706 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/chainingFactoryMethods.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/chainingFactoryMethods.success @@ -4,11 +4,11 @@ import com.softwaremill.macwire.autocats._ import cats.effect._ -val created = scala.collection.mutable.Set[String]() +val created = scala.collection.mutable.ListBuffer[String]() object Test { - def theA(): Resource[IO, A] = { created.add("a"); Resource.pure(A()) } - def theB(b: A): Resource[IO, B] = { created.add("b"); Resource.pure(B()) } + def theA(): Resource[IO, A] = Resource.eval(IO { created.append("a"); A() }) + def theB(b: A): Resource[IO, B] = Resource.eval(IO { created.append("b"); B() }) val theC: Resource[IO, C] = autowire[C](theA _, theB _) } @@ -21,5 +21,5 @@ require(theC.a != null) require(theC.b != null) require(created.size == 2) -require(created.contains("a")) -require(created.contains("b")) +require(created(0) == "a") +require(created(1) == "b") diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/ioProvidedParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/ioProvidedParameter.success index 18c8a672..a6671423 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/ioProvidedParameter.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/ioProvidedParameter.success @@ -3,11 +3,11 @@ import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ -val allocated = scala.collection.mutable.Set[String]() +val allocated = scala.collection.mutable.ListBuffer[String]() object Test { - val theA: IO[A] = IO { allocated.add("A"); A() } - val theB: IO[B] = IO { allocated.add("B"); B() } + val theA: IO[A] = IO { allocated.append("A"); A() } + val theB: IO[B] = IO { allocated.append("B"); B() } val theC: Resource[IO, C] = autowire[C](theA, theB) } @@ -19,5 +19,5 @@ val theC: C = { require(theC.a != null) require(theC.b != null) -require(allocated.contains("A")) -require(allocated.contains("B")) \ No newline at end of file +require(allocated(0) == "A") +require(allocated(1) == "B") \ No newline at end of file diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/resourceProvidedParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/resourceProvidedParameters.success index af5520ad..c6aec1ea 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/resourceProvidedParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/resourceProvidedParameters.success @@ -3,11 +3,11 @@ import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ -val allocated = scala.collection.mutable.Set[String]() +val allocated = scala.collection.mutable.ListBuffer[String]() object Test { - val theA: Resource[IO, A] = Resource.make(IO { allocated.add("A"); A() })(_ => IO.unit) - val theB: Resource[IO, B] = Resource.make(IO { allocated.add("B"); B() })(_ => IO.unit) + val theA: Resource[IO, A] = Resource.make(IO { allocated.append("A"); A() })(_ => IO.unit) + val theB: Resource[IO, B] = Resource.make(IO { allocated.append("B"); B() })(_ => IO.unit) val theC: Resource[IO, C] = autowire[C](theA, theB) } @@ -19,5 +19,5 @@ val theC: C = { require(theC.a != null) require(theC.b != null) -require(allocated.contains("A")) -require(allocated.contains("B")) \ No newline at end of file +require(allocated(0) == "A") +require(allocated(1) == "B") \ No newline at end of file From 190fa64c720362118f5865a6c3ad9ea633991f96 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Wed, 3 Nov 2021 21:00:08 +0100 Subject: [PATCH 4/8] Add resource reusability in factory methods --- .../reusingResourcesInFactoryMethods.success | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 macrosAutoCatsTests/src/test/resources/test-cases/reusingResourcesInFactoryMethods.success diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/reusingResourcesInFactoryMethods.success b/macrosAutoCatsTests/src/test/resources/test-cases/reusingResourcesInFactoryMethods.success new file mode 100644 index 00000000..21cc7040 --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/reusingResourcesInFactoryMethods.success @@ -0,0 +1,29 @@ +import com.softwaremill.macwire.autocats._ +import cats.effect._ + +import scala.collection.mutable.ListBuffer + +case class A() +class B() +case class C(b: B) +case class D(b: B) +case class E(c: C, d: D) + +val created = scala.collection.mutable.Set[String]() + +object Test { + def makeB(a: A): Resource[IO, B] = Resource.eval(IO{ created.add("b"); new B()}) + + val theE = autowire[E](makeB _) + +} + +val theE: E = { + import cats.effect.unsafe.implicits.global + Test.theE.allocated.unsafeRunSync()._1 +} + +require(theE.c != null) +require(theE.d != null) +require(created.size == 1) +require(created.contains("b")) From 21639ff1caf41c66cd30ddd7caba87ad4e99ff85 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Wed, 3 Nov 2021 21:08:07 +0100 Subject: [PATCH 5/8] Fallback to autowire in factory method --- .../softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala index 13a599d3..74e5502b 100644 --- a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala @@ -139,7 +139,7 @@ lazy val tt = `type` providers.foldLeft(init) { case (v, fm: FactoryMethod) => { - v :+ fm.result(findProviderIn(v)(_).getOrElse(c.abort(c.enclosingPosition, "TODO2"))) + v :+ fm.result(x => findProviderIn(v)(x).getOrElse(q"com.softwaremill.macwire.autowire[$x](..${v.map(_.ident)})")) } case (v, _ ) => v }.collect { From 75942fce4b87a86014cbae780dba0246c2cf17c6 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Tue, 9 Nov 2021 19:51:50 +0100 Subject: [PATCH 6/8] Extract providers --- .../autocats/MacwireAutoCatsMacros.scala | 227 +++++++++++++----- 1 file changed, 168 insertions(+), 59 deletions(-) diff --git a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala index 74e5502b..05ed06d9 100644 --- a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala @@ -17,6 +17,87 @@ object MacwireCatsEffectMacros { val targetType = implicitly[c.WeakTypeTag[T]] lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) + val providersEntities = new Providers[c.type](c) + import providersEntities._ + + def providerFromExpr(expr: Expr[Any]): Provider = { + val tree = expr.tree + (Resource.fromTree(tree) orElse Effect.fromTree(tree) orElse FactoryMethod.fromTree(tree)) + .getOrElse(new Instance(tree)) + } + + def findProviderIn(values: Seq[Value])(tpe: Type): Option[Tree] = values.find(_.`type` <:< tpe).map(_.ident) + + val providers = dependencies.map(providerFromExpr) + val values: Seq[Value] = { + val init: Seq[Value] = providers.flatMap { + case e: Effect => Some(e.asResource) + case v: Value => Some(v) + case _ => None + } + + providers + .foldLeft(init) { + case (v, fm: FactoryMethod) => { + v :+ fm.result(x => + findProviderIn(v)(x).getOrElse(q"com.softwaremill.macwire.autowire[$x](..${v.map(_.ident)})") + ) + } + case (v, _) => v + } + .collect { case v: Value => + v + } + } + + log(s"exprs: s[${dependencies.mkString(", ")}]") + log(s"Providers: [${providers.mkString(", ")}]") + log(s"Values: [${values.mkString(", ")}]") + + def findProvider(tpe: Type): Option[Tree] = findProviderIn(values)(tpe) + + def isWireable(tpe: Type): Boolean = { + val name = tpe.typeSymbol.fullName + + !name.startsWith("java.lang.") && !name.startsWith("scala.") + } + + lazy val resolutionWithFallback: (Symbol, Type) => Tree = (_, tpe) => + if (isWireable(tpe)) findProvider(tpe).getOrElse(go(tpe)) + else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") + + def go(t: Type): Tree = { + + val r = + (ConstructorCrimper.constructorTree(c, log)(t, resolutionWithFallback) orElse CompanionCrimper + .applyTree(c, log)(t, resolutionWithFallback)) getOrElse + c.abort(c.enclosingPosition, s"Failed for [$t]") + + log(s"Constructed [$r]") + r + } + + val resources: Seq[Resource] = values.collect { + case r: Resource => r + case e: Effect => e.asResource + } + + val code = resources.foldRight( + q"cats.effect.Resource.pure[cats.effect.IO, $targetType](com.softwaremill.macwire.autowire[$targetType](..${values + .map(_.ident)}))" + ) { case (resource, acc) => + q"${resource.value}.flatMap((${resource.ident}: ${resource.`type`}) => $acc)" + } + log(s"Code: [$code]") + + c.Expr[CatsResource[IO, T]](code) + } + + class Providers[C <: blackbox.Context](val con: C) { + import con.universe._ + + lazy val typeCheckUtil = new TypeCheckUtil[con.type](con, log) + sealed trait Provider { def `type`: Type } @@ -28,8 +109,9 @@ object MacwireCatsEffectMacros { class Resource(valueL: => Tree) extends Value { lazy val value = valueL - lazy val `type`: Type = Resource.underlyingType(typeCheckUtil.typeCheckIfNeeded(value)).getOrElse(c.abort(c.enclosingPosition, "TODO")) - lazy val ident: Tree = Ident(TermName(c.freshName())) + lazy val `type`: Type = + Resource.underlyingType(typeCheckUtil.typeCheckIfNeeded(value)).getOrElse(con.abort(con.enclosingPosition, "TODO")) + lazy val ident: Tree = Ident(TermName(con.freshName())) // lazy val tpe = typeCheckUtil.typeCheckIfNeeded(value) } @@ -39,7 +121,7 @@ object MacwireCatsEffectMacros { if (isResource(typeCheckUtil.typeCheckIfNeeded(tree))) Some(new Resource(tree)) else None - def underlyingType(tpe: Type): Option[Type] = if(isResource(tpe)) Some(tpe.typeArgs(1)) else None + def underlyingType(tpe: Type): Option[Type] = if (isResource(tpe)) Some(tpe.typeArgs(1)) else None def isResource(tpe: Type): Boolean = tpe.typeSymbol.fullName.startsWith("cats.effect.kernel.Resource") && tpe.typeArgs.size == 2 @@ -57,7 +139,7 @@ object MacwireCatsEffectMacros { val resultTree = applyWith(resolver) -val t = fun.symbol.asMethod.returnType + val t = fun.symbol.asMethod.returnType if (Resource.isResource(t)) new Resource(resultTree) { override lazy val `type`: Type = tt } @@ -72,7 +154,7 @@ val t = fun.symbol.asMethod.returnType val resultType = fun.symbol.asMethod.returnType (Resource.underlyingType(resultType) orElse Effect.underlyingType(resultType)).getOrElse(resultType) } -lazy val tt = `type` + lazy val tt = `type` def applyWith(resolver: Type => Tree): Tree = { val values = params.map { case ValDef(_, name, tpt, rhs) => resolver(typeCheckUtil.typeCheckIfNeeded(tpt)) @@ -99,7 +181,7 @@ lazy val tt = `type` class Effect(value: Tree) extends Value { - override def ident: Tree = asResource.ident//???? + override def ident: Tree = asResource.ident //???? override lazy val `type`: Type = typeCheckUtil.typeCheckIfNeeded(value).typeArgs(0) @@ -114,78 +196,105 @@ lazy val tt = `type` if (isEffect(typeCheckUtil.typeCheckIfNeeded(tree))) Some(new Effect(tree)) else None - def underlyingType(tpe: Type): Option[Type] = if(isEffect(tpe)) Some(tpe.typeArgs(0)) else None + def underlyingType(tpe: Type): Option[Type] = if (isEffect(tpe)) Some(tpe.typeArgs(0)) else None def isEffect(tpe: Type): Boolean = tpe.typeSymbol.fullName.startsWith("cats.effect.IO") && tpe.typeArgs.size == 1 } - def providerFromExpr(expr: Expr[Any]): Provider = { - val tree = expr.tree - (Resource.fromTree(tree) orElse Effect.fromTree(tree) orElse FactoryMethod.fromTree(tree)) - .getOrElse(new Instance(tree)) - } + } - def findProviderIn(values: Seq[Value])(tpe: Type): Option[Tree] = values.find(_.`type` <:< tpe).map(_.ident) + // def autowirepure_impl[T: c.WeakTypeTag]( + // c: blackbox.Context + // )(dependencies: c.Expr[Any]*): c.Expr[T] = { + // import c.universe._ - val providers = dependencies.map(providerFromExpr) - val values: Seq[Value] = { - val init: Seq[Value] = providers.flatMap { - case e: Effect => Some(e.asResource) - case v: Value => Some(v) - case _ => None - } + // val targetType = implicitly[c.WeakTypeTag[T]] + // lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) - providers.foldLeft(init) { - case (v, fm: FactoryMethod) => { - v :+ fm.result(x => findProviderIn(v)(x).getOrElse(q"com.softwaremill.macwire.autowire[$x](..${v.map(_.ident)})")) - } - case (v, _ ) => v - }.collect { - case v: Value => v - } - } + // sealed trait Provider { + // def `type`: Type + // } - log(s"exprs: s[${dependencies.mkString(", ")}]") - log(s"Providers: [${providers.mkString(", ")}]") - log(s"Values: [${values.mkString(", ")}]") + // case class Instance(value: Tree) extends Provider { + // lazy val `type`: Type = typeCheckUtil.typeCheckIfNeeded(value) + // lazy val ident: Tree = value + // } - def findProvider(tpe: Type): Option[Tree] = findProviderIn(values)(tpe) + // class FactoryMethod(params: List[ValDef], fun: Tree) extends Provider { - def isWireable(tpe: Type): Boolean = { - val name = tpe.typeSymbol.fullName + // def result(resolver: Type => Tree): Instance = new Instance(applyWith(resolver)) - !name.startsWith("java.lang.") && !name.startsWith("scala.") - } + // lazy val `type`: Type = fun.symbol.asMethod.returnType - lazy val resolutionWithFallback: (Symbol, Type) => Tree = (_, tpe) => - if (isWireable(tpe)) findProvider(tpe).getOrElse(go(tpe)) - else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") + // def applyWith(resolver: Type => Tree): Tree = { + // val values = params.map { case ValDef(_, name, tpt, rhs) => + // resolver(typeCheckUtil.typeCheckIfNeeded(tpt)) + // } - def go(t: Type): Tree = { + // q"$fun(..$values)" + // } - val r = - (ConstructorCrimper.constructorTree(c, log)(t, resolutionWithFallback) orElse CompanionCrimper - .applyTree(c, log)(t, resolutionWithFallback)) getOrElse - c.abort(c.enclosingPosition, s"Failed for [$t]") + // } - log(s"Constructed [$r]") - r - } + // object FactoryMethod { + // def fromTree(tree: Tree): Option[FactoryMethod] = tree match { + // // Function with two parameter lists (implicit parameters) (<2.13) + // case Block(Nil, Function(p, Apply(Apply(f, _), _))) => Some(new FactoryMethod(p, f)) + // case Block(Nil, Function(p, Apply(f, _))) => Some(new FactoryMethod(p, f)) + // // Function with two parameter lists (implicit parameters) (>=2.13) + // case Function(p, Apply(Apply(f, _), _)) => Some(new FactoryMethod(p, f)) + // case Function(p, Apply(f, _)) => Some(new FactoryMethod(p, f)) + // // Other types not supported + // case _ => None + // } - val resources: Seq[Resource] = values.collect { - case r: Resource => r - case e: Effect => e.asResource - } + // } - val code = resources.foldRight(q"cats.effect.Resource.pure[cats.effect.IO, $targetType](com.softwaremill.macwire.autowire[$targetType](..${values.map(_.ident)}))") { - case (resource, acc) => - q"${resource.value}.flatMap((${resource.ident}: ${resource.`type`}) => $acc)" - } - log(s"Code: [$code]") + // def providerFromExpr(expr: Expr[Any]): Provider = { + // val tree = expr.tree + // FactoryMethod + // .fromTree(tree) + // .getOrElse(new Instance(tree)) + // } + + // val providers = dependencies.map(providerFromExpr) + + // log(s"PURE exprs: s[${dependencies.mkString(", ")}]") + // log(s"PURE Providers: [${providers.mkString(", ")}]") + // log(s"PURE ProvidersTYPES: [${providers.map(_.`type`).mkString(", ")}]") + + // def findProvider(tpe: Type): Option[Tree] = providers.find(_.`type` <:< tpe).map { + // case i: Instance => i.ident + // case fm: FactoryMethod => fm.result(findProvider(_).getOrElse(c.abort(c.enclosingPosition, "TODO3"))).ident + // } + + // val code = go(targetType.tpe) + // log(s"Code: [$code]") + + // c.Expr[T](code) + // } + + // private def wireWithResolver[T: c.WeakTypeTag]( + // c: blackbox.Context + // )(resolver: c.Type => Option[c.Tree]) = { + // import c.universe._ + + // def isWireable(tpe: Type): Boolean = { + // val name = tpe.typeSymbol.fullName + + // !name.startsWith("java.lang.") && !name.startsWith("scala.") + // } + + // lazy val resolutionWithFallback: (Symbol, Type) => Tree = (_, tpe) => + // if (isWireable(tpe)) resolver(tpe).orElse(go(tpe)).getOrElse(c.abort(c.enclosingPosition, s"TODO???")) + // else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") + + // def go(t: Type): Option[Tree] = + // (ConstructorCrimper.constructorTree(c, log)(t, resolutionWithFallback) orElse CompanionCrimper + // .applyTree(c, log)(t, resolutionWithFallback)) - c.Expr[CatsResource[IO, T]](code) - } + // } } From 91a74a9ce4d00c5acc6f77eb0575f2ef1dcb8587 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Mon, 15 Nov 2021 19:22:33 +0100 Subject: [PATCH 7/8] Sort incomming factory methods dependencies --- .../softwaremill/macwire/MacwireMacros.scala | 31 +++---- .../macwire/internals/CompanionCrimper.scala | 28 +++++++ .../internals/ConstructorCrimper.scala | 27 ++++++ .../autocats/MacwireAutoCatsMacros.scala | 82 +++++++++++-------- .../test-cases/sortFactoryMethods.success | 29 +++++++ .../todo/ambiguesTraitImplementation.failure | 15 ++++ 6 files changed, 161 insertions(+), 51 deletions(-) create mode 100644 macrosAutoCatsTests/src/test/resources/test-cases/sortFactoryMethods.success create mode 100644 macrosAutoCatsTests/src/test/resources/test-cases/todo/ambiguesTraitImplementation.failure diff --git a/macros/src/main/scala-2/com/softwaremill/macwire/MacwireMacros.scala b/macros/src/main/scala-2/com/softwaremill/macwire/MacwireMacros.scala index 5795b8f9..88a29912 100644 --- a/macros/src/main/scala-2/com/softwaremill/macwire/MacwireMacros.scala +++ b/macros/src/main/scala-2/com/softwaremill/macwire/MacwireMacros.scala @@ -212,6 +212,17 @@ object MacwireMacros { case i: Instance => i.ident case fm: FactoryMethod => fm.result(findProvider(_).getOrElse(c.abort(c.enclosingPosition, "TODO3"))).ident } + + val code = wireWithResolver(c)(findProvider(_)) getOrElse c.abort(c.enclosingPosition, s"Failed for [$targetType]")//FIXME Improve error tracing + log(s"Code: [$code]") + + c.Expr[T](code) + } + + def wireWithResolver[T: c.WeakTypeTag]( + c: blackbox.Context + )(resolver: c.Type => Option[c.Tree]) = { + import c.universe._ def isWireable(tpe: Type): Boolean = { val name = tpe.typeSymbol.fullName @@ -220,25 +231,15 @@ object MacwireMacros { } lazy val resolutionWithFallback: (Symbol, Type) => Tree = (_, tpe) => - if (isWireable(tpe)) findProvider(tpe).getOrElse(go(tpe)) + if (isWireable(tpe)) resolver(tpe).orElse(go(tpe)).getOrElse(c.abort(c.enclosingPosition, s"TODO???")) else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") - def go(t: Type): Tree = { - - val r = - (ConstructorCrimper.constructorTree(c, log)(t, resolutionWithFallback) orElse CompanionCrimper - .applyTree(c, log)(t, resolutionWithFallback)) getOrElse - c.abort(c.enclosingPosition, s"Failed for [$t]") + def go(t: Type): Option[Tree] = + (ConstructorCrimper.constructorTree(c, log)(t, resolutionWithFallback) orElse CompanionCrimper + .applyTree(c, log)(t, resolutionWithFallback)) - log(s"Constructed [$r]") - r - } - - val code = go(targetType.tpe) - log(s"Code: [$code]") - - c.Expr[T](code) + go(implicitly[c.WeakTypeTag[T]].tpe) } } diff --git a/macros/src/main/scala-2/com/softwaremill/macwire/internals/CompanionCrimper.scala b/macros/src/main/scala-2/com/softwaremill/macwire/internals/CompanionCrimper.scala index fcb57fa1..91552e8a 100644 --- a/macros/src/main/scala-2/com/softwaremill/macwire/internals/CompanionCrimper.scala +++ b/macros/src/main/scala-2/com/softwaremill/macwire/internals/CompanionCrimper.scala @@ -69,4 +69,32 @@ object CompanionCrimper { } yield pl.foldLeft(applyMethod)((acc: Tree, args: List[Tree]) => Apply(acc, args)) } + def applyTreeV2[C <: blackbox.Context]( + c: C, + log: Logger + )(targetType: c.Type, resolver: (c.Symbol, c.Type) => Option[c.Tree]): Option[c.Tree] = { + import c.universe._ + + lazy val apply: Option[Symbol] = CompanionCrimper + .applies(c, log)(targetType) + .flatMap(_ match { + case applyMethod :: Nil => Some(applyMethod) + case _ => None + }) + + lazy val applySelect: Option[Select] = apply.map(a => Select(Ident(targetType.typeSymbol.companion), a)) + + lazy val applyParamLists: Option[List[List[Symbol]]] = apply.map(_.asMethod.paramLists) + + def wireParams(paramLists: List[List[Symbol]]): List[List[Option[Tree]]] = + paramLists.map(_.map(p => resolver(p, p.typeSignature))) + + def applyArgs: Option[List[List[Option[Tree]]]] = applyParamLists.map(x => wireParams(x)) + + for { + pl: List[List[Tree]] <- applyArgs.flatMap(x => sequence(x.map(sequence))) + applyMethod: Tree <- applySelect + } yield pl.foldLeft(applyMethod)((acc: Tree, args: List[Tree]) => Apply(acc, args)) + } + } diff --git a/macros/src/main/scala-2/com/softwaremill/macwire/internals/ConstructorCrimper.scala b/macros/src/main/scala-2/com/softwaremill/macwire/internals/ConstructorCrimper.scala index dc6c03e3..e8a345e5 100644 --- a/macros/src/main/scala-2/com/softwaremill/macwire/internals/ConstructorCrimper.scala +++ b/macros/src/main/scala-2/com/softwaremill/macwire/internals/ConstructorCrimper.scala @@ -137,4 +137,31 @@ object ConstructorCrimper { constructorArgs.map(_.foldLeft(constructionMethodTree)((acc: Tree, args: List[Tree]) => Apply(acc, args))) } } + + def constructorTreeV2[C <: blackbox.Context]( + c: C, + log: Logger + )(targetType: c.Type, resolver: (c.Symbol, c.Type) => Option[c.Tree]): Option[c.Tree] = { + import c.universe._ + + lazy val targetTypeD: Type = targetType.dealias + + lazy val constructor: Option[Symbol] = ConstructorCrimper.constructor(c, log)(targetType) + + lazy val constructorParamLists: Option[List[List[Symbol]]] = + constructor.map(_.asMethod.paramLists.filterNot(_.headOption.exists(_.isImplicit))) + + def constructorArgs: Option[List[List[Option[Tree]]]] = log.withBlock("Looking for targetConstructor arguments") { + constructorParamLists.map(wireConstructorParams(_)) + } + + def wireConstructorParams(paramLists: List[List[Symbol]]): List[List[Option[Tree]]] = + paramLists.map(_.map(p => resolver(p, /*SI-4751*/ paramType(c)(targetTypeD, p)))) + + log.withBlock(s"Creating Constructor Tree for $targetType") { + val constructionMethodTree: Tree = Select(New(Ident(targetTypeD.typeSymbol)), termNames.CONSTRUCTOR) + val flattenConstructorArgs = constructorArgs.flatMap(l => sequence(l.map(sequence)))//FIXME it breaks the current macwire's philosophy.... + flattenConstructorArgs.map(_.foldLeft(constructionMethodTree)((acc: Tree, args: List[Tree]) => Apply(acc, args))) + } + } } diff --git a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala index 05ed06d9..8be81d34 100644 --- a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala @@ -3,6 +3,8 @@ package com.softwaremill.macwire.autocats import scala.reflect.macros.blackbox import cats.effect.{IO, Resource => CatsResource} import com.softwaremill.macwire.internals._ +import cats.implicits._ +import com.softwaremill.macwire.MacwireMacros object MacwireCatsEffectMacros { private val log = new Logger() @@ -29,25 +31,35 @@ object MacwireCatsEffectMacros { def findProviderIn(values: Seq[Value])(tpe: Type): Option[Tree] = values.find(_.`type` <:< tpe).map(_.ident) val providers = dependencies.map(providerFromExpr) + val values: Seq[Value] = { - val init: Seq[Value] = providers.flatMap { - case e: Effect => Some(e.asResource) - case v: Value => Some(v) - case _ => None + + val (fms, initValues): (Vector[FactoryMethod], Vector[Value]) = providers.toVector.partitionBifold { + case e: Effect => Right(e.asResource) + case v: Value => Right(v) + case fm: FactoryMethod => Left(fm) } - providers - .foldLeft(init) { - case (v, fm: FactoryMethod) => { - v :+ fm.result(x => - findProviderIn(v)(x).getOrElse(q"com.softwaremill.macwire.autowire[$x](..${v.map(_.ident)})") - ) - } - case (v, _) => v - } - .collect { case v: Value => - v - } + + def freshInstanceFromEmptyConstructor(t: Type): Option[Tree] = { + (ConstructorCrimper.constructorTreeV2(c, log)(t, (_, _) => None) orElse CompanionCrimper + .applyTreeV2(c, log)(t, (_, _) => None)) + } + + def go(fms: Vector[FactoryMethod], values: Vector[Value]): Vector[Value] = fms match { + case Vector() => values + case v => { + log(s"Resolving for FMs [${fms.mkString(", ")}] with values [${values.mkString(", ")}]") + //FIXME we need to make use of empty constructors at this point :/ + val (remainingFms, appliedFms) = v.partitionBifold(f => f.maybeResult(t => findProviderIn(values)(t).orElse(freshInstanceFromEmptyConstructor(t))).toRight(f)) + if (appliedFms.isEmpty) c.abort(c.enclosingPosition, "Failed to apply any factory method") + + go(remainingFms, values ++ appliedFms) + + } + } + + go(fms, initValues) } log(s"exprs: s[${dependencies.mkString(", ")}]") @@ -150,6 +162,19 @@ object MacwireCatsEffectMacros { } + def maybeResult(resolver: Type => Option[Tree]): Option[Value] = + maybeApplyWith(resolver).map { resultTree => + val t = fun.symbol.asMethod.returnType + if (Resource.isResource(t)) new Resource(resultTree) { + override lazy val `type`: Type = tt + } + else if (Effect.isEffect(t)) new Effect(resultTree) { + override lazy val `type`: Type = tt + } + else Instance(resultTree) + + } + lazy val `type`: Type = { val resultType = fun.symbol.asMethod.returnType (Resource.underlyingType(resultType) orElse Effect.underlyingType(resultType)).getOrElse(resultType) @@ -163,6 +188,11 @@ object MacwireCatsEffectMacros { q"$fun(..$values)" } + def maybeApplyWith(resolver: Type => Option[Tree]): Option[Tree] = + params.map { case ValDef(_, name, tpt, rhs) => + resolver(typeCheckUtil.typeCheckIfNeeded(tpt)) + }.sequence.map(values => q"$fun(..$values)") + } object FactoryMethod { @@ -276,25 +306,5 @@ object MacwireCatsEffectMacros { // c.Expr[T](code) // } - // private def wireWithResolver[T: c.WeakTypeTag]( - // c: blackbox.Context - // )(resolver: c.Type => Option[c.Tree]) = { - // import c.universe._ - - // def isWireable(tpe: Type): Boolean = { - // val name = tpe.typeSymbol.fullName - // !name.startsWith("java.lang.") && !name.startsWith("scala.") - // } - - // lazy val resolutionWithFallback: (Symbol, Type) => Tree = (_, tpe) => - // if (isWireable(tpe)) resolver(tpe).orElse(go(tpe)).getOrElse(c.abort(c.enclosingPosition, s"TODO???")) - // else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") - - // def go(t: Type): Option[Tree] = - // (ConstructorCrimper.constructorTree(c, log)(t, resolutionWithFallback) orElse CompanionCrimper - // .applyTree(c, log)(t, resolutionWithFallback)) - - - // } } diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/sortFactoryMethods.success b/macrosAutoCatsTests/src/test/resources/test-cases/sortFactoryMethods.success new file mode 100644 index 00000000..6c94c072 --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/sortFactoryMethods.success @@ -0,0 +1,29 @@ +import com.softwaremill.macwire.autocats._ +import cats.effect._ + +case class A() +class B(i: Int) +class C(b: B, s: String) +case class D(c: C) +case class E(c: C, d: D) + +val created = scala.collection.mutable.ListBuffer[String]() + +object Test { + def makeB(a: A): Resource[IO, B] = Resource.eval(IO{ created.append("b"); new B(0)}) + def makeC(b: B): Resource[IO, C] = Resource.eval(IO{ created.append("c"); new C(b, "c")}) + + val theE = autowire[E](makeC _, makeB _) + +} + +val theE: E = { + import cats.effect.unsafe.implicits.global + Test.theE.allocated.unsafeRunSync()._1 +} + +require(theE.c != null) +require(theE.d != null) +require(created.size == 2) +require(created(0) == "b") +require(created(1) == "c") diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/todo/ambiguesTraitImplementation.failure b/macrosAutoCatsTests/src/test/resources/test-cases/todo/ambiguesTraitImplementation.failure new file mode 100644 index 00000000..e28e5503 --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/todo/ambiguesTraitImplementation.failure @@ -0,0 +1,15 @@ +import com.softwaremill.macwire.autocats._ + +trait T +case class A(t: T) extends T +class B() extends T + +object Test { + val theA = autowire[A](new B()) + +} + +val theA: A = { + import cats.effect.unsafe.implicits.global + Test.theB.allocated.unsafeRunSync()._1 +} From cc584a63c7a3ae50b6feeb5d1394296dec0ebaf4 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Mon, 15 Nov 2021 21:20:25 +0100 Subject: [PATCH 8/8] Mixed factory method params test --- .../mixedFactoryMethodParams.success | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 macrosAutoCatsTests/src/test/resources/test-cases/mixedFactoryMethodParams.success diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/mixedFactoryMethodParams.success b/macrosAutoCatsTests/src/test/resources/test-cases/mixedFactoryMethodParams.success new file mode 100644 index 00000000..c169d32e --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/mixedFactoryMethodParams.success @@ -0,0 +1,30 @@ +import com.softwaremill.macwire.autocats._ +import cats.effect._ + +class A() +class B() +class C(a: A, b: B) +case class D(c: C) + +val created = scala.collection.mutable.ListBuffer[String]() + +object Test { + + val ioA: IO[A] = IO {created.append("a"); new A()} + val resourceB: Resource[IO, B] = Resource.eval(IO {created.append("b"); new B()}) + def makeC(a: A, b: B): Resource[IO, C] = Resource.eval(IO{ created.append("c"); new C(a, b)}) + + val theD = autowire[D](ioA, resourceB, makeC _) + +} + +val theD: D = { + import cats.effect.unsafe.implicits.global + Test.theD.allocated.unsafeRunSync()._1 +} + +require(theD.c != null) +require(created.size == 3) +require(created(0) == "a") +require(created(1) == "b") +require(created(2) == "c")