From 87c4e2a51caaf73335d55a3112955c4d09a14e0c Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Wed, 4 Aug 2021 21:12:18 +0200 Subject: [PATCH 01/32] init work --- build.sbt | 21 ++++- .../MacwireCatsEffectMacros.scala | 80 +++++++++++++++++++ .../NewCompanionCrimper.scala | 51 ++++++++++++ .../macwire/catseffectsupport/package.scala | 7 ++ .../test-cases/wireResourceRec-flat.success | 13 +++ .../wireResourceRec-missing-deps.failure | 12 +++ .../catseffectsupport/CompileTests.scala | 13 +++ 7 files changed, 196 insertions(+), 1 deletion(-) create mode 100644 macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala create mode 100644 macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/NewCompanionCrimper.scala create mode 100644 macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-flat.success create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure create mode 100644 macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala diff --git a/build.sbt b/build.sbt index d2567d04..6521846e 100644 --- a/build.sbt +++ b/build.sbt @@ -64,6 +64,8 @@ val scalatest = "org.scalatest" %% "scalatest" % "3.2.9" val javassist = "org.javassist" % "javassist" % "3.28.0-GA" val akkaActor = "com.typesafe.akka" %% "akka-actor" % "2.6.15" val javaxInject = "javax.inject" % "javax.inject" % "1" +val cats = "org.typelevel" %% "cats-core" % "2.6.1" +val catsEffect = "org.typelevel" %% "cats-effect" % "3.2.1" lazy val root = project .in(file(".")) @@ -79,7 +81,9 @@ lazy val root = project testUtil, utilTests, macrosAkka, - macrosAkkaTests + macrosAkkaTests, + macrosCatsEffect, + macrosCatsEffectTests ).flatMap(_.projectRefs): _* ) @@ -167,6 +171,21 @@ lazy val macrosAkkaTests = projectMatrix .dependsOn(macrosAkka, testUtil) .jvmPlatform(scalaVersions = scala2) + lazy val macrosCatsEffect = projectMatrix + .in(file("macrosCatsEffect")) + .settings(commonSettings) + .settings(libraryDependencies ++= Seq(catsEffect, cats)) + .dependsOn(macros) + .jvmPlatform(scalaVersions = scala2) + .jsPlatform(scalaVersions = scala2) + + lazy val macrosCatsEffectTests = projectMatrix + .in(file("macrosCatsEffectTests")) + .settings(testSettings) + .settings(libraryDependencies ++= Seq(scalatest, catsEffect)) + .dependsOn(macrosCatsEffect, testUtil) + .jvmPlatform(scalaVersions = scala2) + Compile / compile := { // Enabling debug project-wide. Can't find a better way to pass options to scalac. System.setProperty("macwire.debug", "") diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala new file mode 100644 index 00000000..2b69cedd --- /dev/null +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -0,0 +1,80 @@ +package com.softwaremill.macwire +package catseffectsupport + +import scala.reflect.macros.blackbox +import cats.effect._ +import com.softwaremill.macwire.internals._ + +object MacwireCatsEffectMacros { + private val log = new Logger() + + /** + * wire as usual, but when we've got an instance which may be created as resource we use it, but do not acquire the resource till it's possible to delay it (till we've got all required components) + + * + */ + def wireResourceRec_impl[T](c: blackbox.Context): c.Expr[Resource[IO, T]] = { + import c.universe._ + implicit val wttp = weakTypeTag[Resource[IO, T]] + + def isWireable(tpe: Type): Boolean = { + val name = tpe.typeSymbol.fullName + + !name.startsWith("java.lang.") && !name.startsWith("scala.") + } + + def isResource(tpe: Type): Boolean = { + val name = tpe.typeSymbol.fullName + + !name.startsWith("cats.Resource.") + } + + val dependencyResolver = new DependencyResolver[c.type, Type, Tree](c, log)(tpe => + if(isResource(tpe)) c.Expr[T](q"wireResourceRec[$tpe]").tree + else if (!isWireable(tpe)) c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") + else c.Expr[T](q"wireResourceRec[cats.Resource[cats.effect.IO, $tpe]]").tree + ) + + val constructorCrimper = new ConstructorCrimper[c.type, Resource[IO, T]](c, log) + val companionCrimper = new CompanionCrimper[c.type, Resource[IO, T]](c, log) + + lazy val targetType = companionCrimper.targetType.toString + + val code: Tree = (companionCrimper.applyTree(dependencyResolver) orElse constructorCrimper.constructorTree(dependencyResolver)) getOrElse + c.abort(c.enclosingPosition, "Err") + println(s"Generated code: ${showCode(code)}, ${showRaw(code)}") + c.Expr(code) + + } + + def wireResource[T: c.WeakTypeTag](c: blackbox.Context)(dependencyResolver: DependencyResolver[c.type, c.universe.Type, c.universe.Tree]): c.Expr[T] = { + import c.universe._ + + val constructorCrimper = new ConstructorCrimper[c.type, T](c, log) + val companionCrimper = new CompanionCrimper[c.type, T](c, log) + + lazy val targetType = companionCrimper.targetType.toString + lazy val whatWasWrong: String = { + if (constructorCrimper.constructor.isEmpty && companionCrimper.companionType.isEmpty) + s"Cannot find a public constructor nor a companion object for [$targetType]" + else if (companionCrimper.applies.isDefined && companionCrimper.applies.get.isEmpty) + s"Companion object for [$targetType] has no apply methods constructing target type." + else if (companionCrimper.applies.isDefined && companionCrimper.applies.get.size > 1) + s"No public primary constructor found for $targetType and multiple matching apply methods in its companion object were found." + else s"Target type not supported for wiring: $targetType. Please file a bug report with your use-case." + } + + val code: Tree = (constructorCrimper.constructorTree(dependencyResolver) orElse companionCrimper.applyTree(dependencyResolver)) getOrElse + c.abort(c.enclosingPosition, whatWasWrong) + log(s"Generated code: ${showCode(code)}, ${showRaw(code)}") + c.Expr(code) + } + + + private def buildDependenciesGraph[T: c.WeakTypeTag](c: blackbox.Context) = { + + } + + + +} diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/NewCompanionCrimper.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/NewCompanionCrimper.scala new file mode 100644 index 00000000..d1df9b94 --- /dev/null +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/NewCompanionCrimper.scala @@ -0,0 +1,51 @@ +package com.softwaremill.macwire.catseffectsupport + +import com.softwaremill.macwire.internals._ + +import scala.reflect.macros.blackbox + +private[macwire] class NewCompanionCrimper [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 + + lazy val companionType: Option[Type] = if(targetType.companion == NoType) None else Some(targetType.companion) + + def isCompanionApply(method: Symbol): Boolean = + method.isMethod && + method.isPublic && + method.asMethod.returnType <:< targetType && + method.asMethod.name.decodedName.toString == "apply" + + lazy val applies: Option[List[Symbol]] = log.withBlock("Looking for apply methods of Companion Object") { + val as: Option[List[Symbol]] = companionType.map(_.members.filter(isCompanionApply).toList) + as.foreach(x => log.withBlock(s"There are ${x.size} apply methods:" ) { x.foreach(c => log(showApply(c))) }) + as + } + + lazy val apply: Option[Symbol] = applies.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(dependencyResolver: DependencyResolverType)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map { p => + + dependencyResolver.resolve(p, p.typeSignature) + } ) + + def applyArgs(dependencyResolver: DependencyResolverType): Option[List[List[Tree]]] = applyParamLists.map(wireParams(dependencyResolver)) + + def applyTree(dependencyResolver: DependencyResolverType): Option[Tree] = for { + pl: List[List[Tree]] <- applyArgs(dependencyResolver) + applyMethod: Tree <- applySelect + } yield pl.foldLeft(applyMethod)((acc: Tree, args: List[Tree]) => Apply(acc, args)) + + def showApply(c: Symbol): String = c.asMethod.typeSignature.toString +} + diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala new file mode 100644 index 00000000..04c3bb61 --- /dev/null +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala @@ -0,0 +1,7 @@ +package com.softwaremill.macwire + +import cats.effect._ + +package object catseffectsupport { + def wireResourceRec[A]: Resource[IO, A] = macro MacwireCatsEffectMacros.wireResourceRec_impl[A] +} \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-flat.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-flat.success new file mode 100644 index 00000000..5ccb06fb --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-flat.success @@ -0,0 +1,13 @@ +import cats.effect._ + +class DatabaseAccess() +class SecurityFilter() +class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) +class UserStatusReader(userFinder: UserFinder) + +object UserModule { + import com.softwaremill.catseffectsupport._ + + val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) + val theUserStatusReader: Resource[IO, UserStatusReader] = wireResourceRec[UserStatusReader] +} \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure new file mode 100644 index 00000000..50f89dfa --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure @@ -0,0 +1,12 @@ +import cats.effect._ + +class DatabaseAccess private () +class SecurityFilter private () +class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) +class UserStatusReader(userFinder: UserFinder) + +object UserModule { + import com.softwaremill.catseffectsupport._ + + val theUserStatusReader: Resource[IO, UserStatusReader] = wireResourceRec[UserStatusReader] +} \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala new file mode 100644 index 00000000..ebcd2b41 --- /dev/null +++ b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala @@ -0,0 +1,13 @@ +package com.softwaremill.macwire.catseffectsupport + +import com.softwaremill.macwire.CompileTestsSupport + +class CompileTests extends CompileTestsSupport { + + runTestsWith( + expectedFailures = List( + "wireResourceRec-missing-deps" -> List("Cannot find a value of type: [UserFinder]") + ), + expectedWarnings = List() + ) +} From d414d7d83bdafc14332fd79928cf4e5232825b5a Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Fri, 27 Aug 2021 15:25:45 +0200 Subject: [PATCH 02/32] wire single resource --- build.sbt | 2 +- .../internals/DependencyResolver.scala | 3 +- .../MacwireCatsEffectMacros.scala | 79 +++++-------------- .../macwire/catseffectsupport/package.scala | 3 +- 4 files changed, 23 insertions(+), 64 deletions(-) diff --git a/build.sbt b/build.sbt index 6521846e..1e691425 100644 --- a/build.sbt +++ b/build.sbt @@ -188,7 +188,7 @@ lazy val macrosAkkaTests = projectMatrix Compile / compile := { // Enabling debug project-wide. Can't find a better way to pass options to scalac. - System.setProperty("macwire.debug", "") + System.setProperty("macwire.debug", "true") (Compile / compile).value } diff --git a/macros/src/main/scala-2/com/softwaremill/macwire/internals/DependencyResolver.scala b/macros/src/main/scala-2/com/softwaremill/macwire/internals/DependencyResolver.scala index c2b9eb17..ba188e74 100644 --- a/macros/src/main/scala-2/com/softwaremill/macwire/internals/DependencyResolver.scala +++ b/macros/src/main/scala-2/com/softwaremill/macwire/internals/DependencyResolver.scala @@ -4,7 +4,7 @@ import com.softwaremill.macwire.internals.EligibleValuesFinder.Scope.LocalForwar import scala.reflect.macros.blackbox -private[macwire] class DependencyResolver[C <: blackbox.Context, TypeC <: C#Type, TreeC <: C#Tree](val c: C, debug: Logger)(resolutionFallback: TypeC => TreeC) { +class DependencyResolver[C <: blackbox.Context, TypeC <: C#Type, TreeC <: C#Tree](val c: C, debug: Logger)(resolutionFallback: TypeC => TreeC) { import c.universe._ @@ -17,7 +17,6 @@ private[macwire] class DependencyResolver[C <: blackbox.Context, TypeC <: C#Type * a compilation error is reported and `None` is returned. */ def resolve(param: Symbol, t: Type): Tree = { - eligibleValues.findInFirstScope(t).toList match { case Nil => resolutionFallback(t.asInstanceOf[TypeC]).asInstanceOf[Tree] case value :: Nil => diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala index 2b69cedd..a7f9e591 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -4,77 +4,36 @@ package catseffectsupport import scala.reflect.macros.blackbox import cats.effect._ import com.softwaremill.macwire.internals._ +import java.util.concurrent.atomic.AtomicLong object MacwireCatsEffectMacros { private val log = new Logger() - /** - * wire as usual, but when we've got an instance which may be created as resource we use it, but do not acquire the resource till it's possible to delay it (till we've got all required components) - - * - */ - def wireResourceRec_impl[T](c: blackbox.Context): c.Expr[Resource[IO, T]] = { + def wireResourceRec_impl[T: c.WeakTypeTag, A: c.WeakTypeTag](c: blackbox.Context)(resources: c.Expr[Resource[IO, Any]]*): c.Expr[A] = { import c.universe._ - implicit val wttp = weakTypeTag[Resource[IO, T]] + val targetType = implicitly[c.WeakTypeTag[T]] + lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) - def isWireable(tpe: Type): Boolean = { - val name = tpe.typeSymbol.fullName - - !name.startsWith("java.lang.") && !name.startsWith("scala.") + val rr = resources.map { er => + val name = Ident(TermName(c.freshName())) + val resourceType = typeCheckUtil.typeCheckIfNeeded(er.tree).typeArgs(1)//FIXME + (er, name, resourceType) } - - def isResource(tpe: Type): Boolean = { - val name = tpe.typeSymbol.fullName - - !name.startsWith("cats.Resource.") + + val code = rr.foldRight(MacwireMacros.wire[T](c)(new DependencyResolver2[c.type, Type, Tree](c, log)(rr.map(t => (t._3, t._2)).toMap)).tree) { case ((er, name, tpe), acc) => + println(s"FRESH NAME [$name]") + q"$er.map(($name: $tpe) => $acc)" } - - val dependencyResolver = new DependencyResolver[c.type, Type, Tree](c, log)(tpe => - if(isResource(tpe)) c.Expr[T](q"wireResourceRec[$tpe]").tree - else if (!isWireable(tpe)) c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") - else c.Expr[T](q"wireResourceRec[cats.Resource[cats.effect.IO, $tpe]]").tree - ) - - val constructorCrimper = new ConstructorCrimper[c.type, Resource[IO, T]](c, log) - val companionCrimper = new CompanionCrimper[c.type, Resource[IO, T]](c, log) - - lazy val targetType = companionCrimper.targetType.toString - - val code: Tree = (companionCrimper.applyTree(dependencyResolver) orElse constructorCrimper.constructorTree(dependencyResolver)) getOrElse - c.abort(c.enclosingPosition, "Err") - println(s"Generated code: ${showCode(code)}, ${showRaw(code)}") - c.Expr(code) - + println(s"CODE: [$code]") + c.Expr[A](code) } - - def wireResource[T: c.WeakTypeTag](c: blackbox.Context)(dependencyResolver: DependencyResolver[c.type, c.universe.Type, c.universe.Tree]): c.Expr[T] = { +//FIXME + class DependencyResolver2[C <: blackbox.Context, TypeC <: C#Type, TreeC <: C#Tree](override val c: C, debug: Logger)(values: Map[TypeC, TreeC]) extends DependencyResolver[C, TypeC, TreeC](c, debug)(t => c.abort(c.enclosingPosition, s"Cannot find a value of type: [$t]")) { import c.universe._ - val constructorCrimper = new ConstructorCrimper[c.type, T](c, log) - val companionCrimper = new CompanionCrimper[c.type, T](c, log) - - lazy val targetType = companionCrimper.targetType.toString - lazy val whatWasWrong: String = { - if (constructorCrimper.constructor.isEmpty && companionCrimper.companionType.isEmpty) - s"Cannot find a public constructor nor a companion object for [$targetType]" - else if (companionCrimper.applies.isDefined && companionCrimper.applies.get.isEmpty) - s"Companion object for [$targetType] has no apply methods constructing target type." - else if (companionCrimper.applies.isDefined && companionCrimper.applies.get.size > 1) - s"No public primary constructor found for $targetType and multiple matching apply methods in its companion object were found." - else s"Target type not supported for wiring: $targetType. Please file a bug report with your use-case." + override def resolve(param: Symbol, t: Type): Tree = { + println(s"LOOKING FOR PARAM [$param] TYPE [$t]") + values(t.asInstanceOf[TypeC]).asInstanceOf[Tree] } - - val code: Tree = (constructorCrimper.constructorTree(dependencyResolver) orElse companionCrimper.applyTree(dependencyResolver)) getOrElse - c.abort(c.enclosingPosition, whatWasWrong) - log(s"Generated code: ${showCode(code)}, ${showRaw(code)}") - c.Expr(code) } - - - private def buildDependenciesGraph[T: c.WeakTypeTag](c: blackbox.Context) = { - - } - - - } diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala index 04c3bb61..3f7bb64d 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala @@ -3,5 +3,6 @@ package com.softwaremill.macwire import cats.effect._ package object catseffectsupport { - def wireResourceRec[A]: Resource[IO, A] = macro MacwireCatsEffectMacros.wireResourceRec_impl[A] + //hmmm at the beginning we are going to accept here only resources, but we expect to accept here also configs, factory methods, effects and so on. I'd like to express the current state of work with the accepted type as close as possible + def wireResourceRec[T](resources: Resource[IO, Any]*): Resource[IO, T] = macro MacwireCatsEffectMacros.wireResourceRec_impl[T, Resource[IO, T]] } \ No newline at end of file From 2ed2768f4d68cd5146fbd1c8e95d41d1126b6e09 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Fri, 27 Aug 2021 15:59:39 +0200 Subject: [PATCH 03/32] Add support for multiple resources --- .../MacwireCatsEffectMacros.scala | 23 ++++++--- .../NewCompanionCrimper.scala | 51 ------------------- .../test-cases/wireResourceRec-flat.success | 6 +-- .../wireResourceRec-missing-deps.failure | 10 ++-- .../catseffectsupport/CompileTests.scala | 2 +- 5 files changed, 24 insertions(+), 68 deletions(-) delete mode 100644 macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/NewCompanionCrimper.scala diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala index a7f9e591..b0fc7d63 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -16,15 +16,19 @@ object MacwireCatsEffectMacros { val rr = resources.map { er => val name = Ident(TermName(c.freshName())) - val resourceType = typeCheckUtil.typeCheckIfNeeded(er.tree).typeArgs(1)//FIXME + val resourceType = typeCheckUtil.typeCheckIfNeeded(er.tree).typeArgs(1) (er, name, resourceType) } - - val code = rr.foldRight(MacwireMacros.wire[T](c)(new DependencyResolver2[c.type, Type, Tree](c, log)(rr.map(t => (t._3, t._2)).toMap)).tree) { case ((er, name, tpe), acc) => - println(s"FRESH NAME [$name]") - q"$er.map(($name: $tpe) => $acc)" + + val values = rr.map(t => (t._3, t._2)).toMap + val dependencyResolver = new DependencyResolver2[c.type, Type, Tree](c, log)(values) + val generatedInstance = MacwireMacros.wire[T](c)(dependencyResolver) + + val code = rr.foldRight(q"cats.effect.Resource.pure[cats.effect.IO, $targetType]($generatedInstance)") { case ((er, name, tpe), acc) => + log(s"FRESH NAME [$name]") + q"$er.flatMap(($name: $tpe) => $acc)" } - println(s"CODE: [$code]") + log(s"CODE: [$code]") c.Expr[A](code) } //FIXME @@ -32,8 +36,11 @@ object MacwireCatsEffectMacros { import c.universe._ override def resolve(param: Symbol, t: Type): Tree = { - println(s"LOOKING FOR PARAM [$param] TYPE [$t]") - values(t.asInstanceOf[TypeC]).asInstanceOf[Tree] + log(s"LOOKING FOR PARAM [$param] TYPE [$t]") + values.get(t.asInstanceOf[TypeC]) match { + case Some(value) => value.asInstanceOf[Tree] + case None => c.abort(c.enclosingPosition, s"Cannot find a value of type: [$t]") + } } } } diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/NewCompanionCrimper.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/NewCompanionCrimper.scala deleted file mode 100644 index d1df9b94..00000000 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/NewCompanionCrimper.scala +++ /dev/null @@ -1,51 +0,0 @@ -package com.softwaremill.macwire.catseffectsupport - -import com.softwaremill.macwire.internals._ - -import scala.reflect.macros.blackbox - -private[macwire] class NewCompanionCrimper [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 - - lazy val companionType: Option[Type] = if(targetType.companion == NoType) None else Some(targetType.companion) - - def isCompanionApply(method: Symbol): Boolean = - method.isMethod && - method.isPublic && - method.asMethod.returnType <:< targetType && - method.asMethod.name.decodedName.toString == "apply" - - lazy val applies: Option[List[Symbol]] = log.withBlock("Looking for apply methods of Companion Object") { - val as: Option[List[Symbol]] = companionType.map(_.members.filter(isCompanionApply).toList) - as.foreach(x => log.withBlock(s"There are ${x.size} apply methods:" ) { x.foreach(c => log(showApply(c))) }) - as - } - - lazy val apply: Option[Symbol] = applies.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(dependencyResolver: DependencyResolverType)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map { p => - - dependencyResolver.resolve(p, p.typeSignature) - } ) - - def applyArgs(dependencyResolver: DependencyResolverType): Option[List[List[Tree]]] = applyParamLists.map(wireParams(dependencyResolver)) - - def applyTree(dependencyResolver: DependencyResolverType): Option[Tree] = for { - pl: List[List[Tree]] <- applyArgs(dependencyResolver) - applyMethod: Tree <- applySelect - } yield pl.foldLeft(applyMethod)((acc: Tree, args: List[Tree]) => Apply(acc, args)) - - def showApply(c: Symbol): String = c.asMethod.typeSignature.toString -} - diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-flat.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-flat.success index 5ccb06fb..ed41f000 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-flat.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-flat.success @@ -3,11 +3,11 @@ import cats.effect._ class DatabaseAccess() class SecurityFilter() class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) -class UserStatusReader(userFinder: UserFinder) object UserModule { - import com.softwaremill.catseffectsupport._ + import com.softwaremill.macwire.catseffectsupport._ val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) - val theUserStatusReader: Resource[IO, UserStatusReader] = wireResourceRec[UserStatusReader] + val theSecurityFilter: Resource[IO, SecurityFilter] = Resource.pure(new SecurityFilter()) + val theUserFinder: Resource[IO, UserFinder] = wireResourceRec[UserFinder](theDatabaseAccess, theSecurityFilter) } \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure index 50f89dfa..79a2c8a5 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure @@ -1,12 +1,12 @@ import cats.effect._ -class DatabaseAccess private () -class SecurityFilter private () +class DatabaseAccess() +class SecurityFilter() class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) -class UserStatusReader(userFinder: UserFinder) object UserModule { - import com.softwaremill.catseffectsupport._ + import com.softwaremill.macwire.catseffectsupport._ - val theUserStatusReader: Resource[IO, UserStatusReader] = wireResourceRec[UserStatusReader] + val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) + val theUserFinder: Resource[IO, UserFinder] = wireResourceRec[UserFinder](theDatabaseAccess) } \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala index ebcd2b41..616eed3c 100644 --- a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala +++ b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala @@ -6,7 +6,7 @@ class CompileTests extends CompileTestsSupport { runTestsWith( expectedFailures = List( - "wireResourceRec-missing-deps" -> List("Cannot find a value of type: [UserFinder]") + "wireResourceRec-missing-deps" -> List("Cannot find a value of type: [SecurityFilter]") ), expectedWarnings = List() ) From c42f89590c6c7620b40779a101828c51d7be88bd Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Sun, 29 Aug 2021 20:15:08 +0200 Subject: [PATCH 04/32] add wireRec-like composition --- build.sbt | 2 +- .../MacwireCatsEffectMacros.scala | 87 +++++++++++++------ .../macwire/catseffectsupport/package.scala | 3 +- 3 files changed, 65 insertions(+), 27 deletions(-) diff --git a/build.sbt b/build.sbt index 1e691425..6521846e 100644 --- a/build.sbt +++ b/build.sbt @@ -188,7 +188,7 @@ lazy val macrosAkkaTests = projectMatrix Compile / compile := { // Enabling debug project-wide. Can't find a better way to pass options to scalac. - System.setProperty("macwire.debug", "true") + System.setProperty("macwire.debug", "") (Compile / compile).value } diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala index b0fc7d63..66ec5309 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -2,45 +2,82 @@ package com.softwaremill.macwire package catseffectsupport import scala.reflect.macros.blackbox -import cats.effect._ +import cats.effect.{IO, Resource => CatsResource} import com.softwaremill.macwire.internals._ import java.util.concurrent.atomic.AtomicLong +import cats.effect.kernel object MacwireCatsEffectMacros { - private val log = new Logger() + // type ComposableResource[T] = CatsResource[IO, T] + private val log = new Logger() - def wireResourceRec_impl[T: c.WeakTypeTag, A: c.WeakTypeTag](c: blackbox.Context)(resources: c.Expr[Resource[IO, Any]]*): c.Expr[A] = { + def wireResourceRec_impl[T: c.WeakTypeTag]( + c: blackbox.Context + )(dependencies: c.Expr[Any]*): c.Expr[CatsResource[IO, T]] = { import c.universe._ val targetType = implicitly[c.WeakTypeTag[T]] lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) + val rit: c.WeakTypeTag[CatsResource[IO, T]] = implicitly - val rr = resources.map { er => - val name = Ident(TermName(c.freshName())) - val resourceType = typeCheckUtil.typeCheckIfNeeded(er.tree).typeArgs(1) - (er, name, resourceType) - } - val values = rr.map(t => (t._3, t._2)).toMap - val dependencyResolver = new DependencyResolver2[c.type, Type, Tree](c, log)(values) - val generatedInstance = MacwireMacros.wire[T](c)(dependencyResolver) + sealed trait Provider //FIXME name.... + case class Resource(value: Tree) extends Provider { + lazy val resourceType = typeCheckUtil.typeCheckIfNeeded(value).typeArgs(1) + lazy val ident = Ident(TermName(c.freshName())) + lazy val tpe = typeCheckUtil.typeCheckIfNeeded(value) + } - val code = rr.foldRight(q"cats.effect.Resource.pure[cats.effect.IO, $targetType]($generatedInstance)") { case ((er, name, tpe), acc) => - log(s"FRESH NAME [$name]") - q"$er.flatMap(($name: $tpe) => $acc)" + case class FactoryMethod(value: Tree) extends Provider { + lazy val returnType: Type = value match { + case f: Function => f.body.tpe + } + lazy val params = value match { + case f: Function => f.vparams + } } - log(s"CODE: [$code]") - c.Expr[A](code) - } -//FIXME - class DependencyResolver2[C <: blackbox.Context, TypeC <: C#Type, TreeC <: C#Tree](override val c: C, debug: Logger)(values: Map[TypeC, TreeC]) extends DependencyResolver[C, TypeC, TreeC](c, debug)(t => c.abort(c.enclosingPosition, s"Cannot find a value of type: [$t]")) { - import c.universe._ - override def resolve(param: Symbol, t: Type): Tree = { - log(s"LOOKING FOR PARAM [$param] TYPE [$t]") - values.get(t.asInstanceOf[TypeC]) match { - case Some(value) => value.asInstanceOf[Tree] - case None => c.abort(c.enclosingPosition, s"Cannot find a value of type: [$t]") + val (r, fm) = dependencies.partition(dep => + typeCheckUtil + .typeCheckIfNeeded(dep.tree) + .typeConstructor =:= rit.tpe.typeConstructor //FIXME there is a better way to check resource type, I believe + we need to check if `factoryMethods` really contains factory methods + ) + + val resources = r.map(expr => Resource(expr.tree)) + val factoryMethods = fm.map(expr => FactoryMethod(expr.tree)) + + log(s"RESOURCE: [${resources.mkString(", ")}]") + log(s"FACTORY_METHODS: [${factoryMethods.mkString(", ")}]") + + val providers: Map[Type, Provider] = + resources.map(r => (r.resourceType, r)).toMap ++ factoryMethods.map(fm => (fm.returnType, fm)).toMap + + log(s"PROVIDERS: [${providers.mkString(", ")}]") + def findProvider(t: Type): Option[Provider] = providers.get(t) + + def go(t: Type): Tree = findProvider(t) match { + case Some(r: Resource) => r.ident + case Some(fm: FactoryMethod) => { + lazy val paramLists: List[List[Symbol]] = List(fm.params.map(_.symbol)) + + def wireParams(paramLists: List[List[Symbol]]): List[List[Tree]] = + paramLists.map(_.map(p => go(p.typeSignature))) + + val applyArgs: List[List[Tree]] =wireParams(paramLists) + + applyArgs.foldLeft(fm.value)((acc: Tree, args: List[Tree]) => Apply(acc, args)) } + case None => c.abort(c.enclosingPosition, s"Cannot provide a value of type: [$t]") } + + val generatedInstance = go(targetType.tpe) + + val code = resources.foldRight(q"cats.effect.Resource.pure[cats.effect.IO, $targetType]($generatedInstance)") { case (resource, acc) => + + q"${resource.value}.flatMap((${resource.ident}: ${resource.tpe}) => $acc)" + } + log(s"CODE: [$code]") + c.Expr[CatsResource[IO, T]](code) + } + } diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala index 3f7bb64d..aebe9a98 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala @@ -4,5 +4,6 @@ import cats.effect._ package object catseffectsupport { //hmmm at the beginning we are going to accept here only resources, but we expect to accept here also configs, factory methods, effects and so on. I'd like to express the current state of work with the accepted type as close as possible - def wireResourceRec[T](resources: Resource[IO, Any]*): Resource[IO, T] = macro MacwireCatsEffectMacros.wireResourceRec_impl[T, Resource[IO, T]] + // Phase 2 - accept Resource[IO, ?] & factory methods + def wireResourceRec[T](dependencies: Any*): Resource[IO, T] = macro MacwireCatsEffectMacros.wireResourceRec_impl[T] } \ No newline at end of file From 5cc79d416df8e37a483a84f31e3bb90b820dcdd3 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Fri, 8 Oct 2021 21:54:09 +0200 Subject: [PATCH 05/32] use default constructors & companion objects --- .../internals/ConstructorCrimper.scala | 2 - .../macwire/CompanionCrimperTypeBased.scala | 48 +++++++++ .../macwire/ConstructorCrimperTypeBased.scala | 98 +++++++++++++++++++ .../MacwireCatsEffectMacros.scala | 66 +++++++++---- .../macwire/catseffectsupport/package.scala | 1 + 5 files changed, 196 insertions(+), 19 deletions(-) create mode 100644 macrosCatsEffect/src/main/scala/com/softwaremill/macwire/CompanionCrimperTypeBased.scala create mode 100644 macrosCatsEffect/src/main/scala/com/softwaremill/macwire/ConstructorCrimperTypeBased.scala 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 21314b87..d5224022 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 @@ -15,8 +15,6 @@ private[macwire] class ConstructorCrimper[C <: blackbox.Context, T: C#WeakTypeTa // be directly instantiated lazy val targetTypeD: Type = targetType.dealias - lazy val classOfT: c.Expr[Class[T]] = c.Expr[Class[T]](q"classOf[$targetType]") - lazy val publicConstructors: Iterable[Symbol] = { val ctors = targetType.members .filter(m => m.isMethod && m.asMethod.isConstructor && m.isPublic) diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/CompanionCrimperTypeBased.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/CompanionCrimperTypeBased.scala new file mode 100644 index 00000000..a0bbc975 --- /dev/null +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/CompanionCrimperTypeBased.scala @@ -0,0 +1,48 @@ +package com.softwaremill.macwire + +import com.softwaremill.macwire.internals._ + +import scala.reflect.macros.blackbox + +class CompanionCrimperTypeBased [C <: blackbox.Context, TypeC <: C#Type](val c: C, log: Logger, tpe: TypeC) { + import c.universe._ + + type DependencyResolverType = DependencyResolver[c.type, Type, Tree] + + lazy val targetType: Type = tpe.asInstanceOf[Type] + + lazy val companionType: Option[Type] = if(targetType.companion == NoType) None else Some(targetType.companion) + + def isCompanionApply(method: Symbol): Boolean = + method.isMethod && + method.isPublic && + method.asMethod.returnType <:< targetType && + method.asMethod.name.decodedName.toString == "apply" + + lazy val applies: Option[List[Symbol]] = log.withBlock("Looking for apply methods of Companion Object") { + val as: Option[List[Symbol]] = companionType.map(_.members.filter(isCompanionApply).toList) + as.foreach(x => log.withBlock(s"There are ${x.size} apply methods:" ) { x.foreach(c => log(showApply(c))) }) + as + } + + lazy val apply: Option[Symbol] = applies.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(dependencyResolver: DependencyResolverType)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map(p => dependencyResolver.resolve(p, p.typeSignature))) + + def applyArgs(dependencyResolver: DependencyResolverType): Option[List[List[Tree]]] = applyParamLists.map(wireParams(dependencyResolver)) + + def applyTree(dependencyResolver: DependencyResolverType): Option[Tree] = for { + pl: List[List[Tree]] <- applyArgs(dependencyResolver) + applyMethod: Tree <- applySelect + } yield pl.foldLeft(applyMethod)((acc: Tree, args: List[Tree]) => Apply(acc, args)) + + def showApply(c: Symbol): String = c.asMethod.typeSignature.toString +} + diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/ConstructorCrimperTypeBased.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/ConstructorCrimperTypeBased.scala new file mode 100644 index 00000000..b02378d4 --- /dev/null +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/ConstructorCrimperTypeBased.scala @@ -0,0 +1,98 @@ +package com.softwaremill.macwire + +import scala.reflect.macros.blackbox +import com.softwaremill.macwire.internals._ + +class ConstructorCrimperTypeBased[C <: blackbox.Context, TypeC <: C#Type] (val c: C, log: Logger, tpe: TypeC) { + import c.universe._ + + type DependencyResolverType = DependencyResolver[c.type, Type, Tree] + + lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) + + lazy val targetType: Type = tpe.asInstanceOf[Type] + + // 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 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(c => log(showConstructor(c))) } + 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(c => log(showConstructor(c))) } + ctors + } + + lazy val injectConstructor: Option[Symbol] = if(injectConstructors.size > 1) abort(s"Ambiguous constructors annotated with @javax.inject.Inject for type [$targetType]") else injectConstructors.headOption + + lazy val constructor: Option[Symbol] = log.withBlock(s"Looking for constructor for $targetType"){ + val ctor = injectConstructor orElse primaryConstructor + ctor.foreach(ctor => log(s"Found ${showConstructor(ctor)}")) + ctor + } + + lazy val constructorParamLists: Option[List[List[Symbol]]] = constructor.map(_.asMethod.paramLists.filterNot(_.headOption.exists(_.isImplicit))) + + def constructorArgs(dependencyResolver: DependencyResolverType): Option[List[List[Tree]]] = log.withBlock("Looking for targetConstructor arguments") { + constructorParamLists.map(wireConstructorParams(dependencyResolver)) + } + + 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] = log.withBlock(s"Creating Constructor Tree for $targetType"){ + val constructionMethodTree: Tree = Select(New(Ident(targetTypeD.typeSymbol)), termNames.CONSTRUCTOR) + constructorArgs(dependencyResolver).map(_.foldLeft(constructionMethodTree)((acc: Tree, args: List[Tree]) => Apply(acc, args))) + } + + def wireConstructorParams(dependencyResolver: DependencyResolverType)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map(p => dependencyResolver.resolve(p, /*SI-4751*/paramType(p)))) + + def wireConstructorParamsWithImplicitLookups(dependencyResolver: DependencyResolverType)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map { + case i if i.isImplicit => q"implicitly[${paramType(i)}]" + case p => dependencyResolver.resolve(p, /*SI-4751*/ paramType(p)) + }) + + private def paramType(param: Symbol): Type = { + val (sym: Symbol, tpeArgs: List[Type]) = targetTypeD match { + case TypeRef(_, sym, tpeArgs) => (sym, tpeArgs) + case t => abort(s"Target type not supported for wiring: $t. Please file a bug report with your use-case.") + } + val pTpe = param.typeSignature.substituteTypes(sym.asClass.typeParams, tpeArgs) + if (param.asTerm.isByNameParam) pTpe.typeArgs.head else pTpe + } + + /** + * 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$") + + def showConstructor(c: Symbol): String = c.asMethod.typeSignature.toString + + def abort(msg: String): Nothing = c.abort(c.enclosingPosition, msg) +} diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala index 66ec5309..e163d469 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -6,6 +6,7 @@ import cats.effect.{IO, Resource => CatsResource} import com.softwaremill.macwire.internals._ import java.util.concurrent.atomic.AtomicLong import cats.effect.kernel +import java.util.concurrent.atomic.AtomicInteger object MacwireCatsEffectMacros { // type ComposableResource[T] = CatsResource[IO, T] @@ -50,24 +51,37 @@ object MacwireCatsEffectMacros { val providers: Map[Type, Provider] = resources.map(r => (r.resourceType, r)).toMap ++ factoryMethods.map(fm => (fm.returnType, fm)).toMap - + val counter = new AtomicInteger(0) log(s"PROVIDERS: [${providers.mkString(", ")}]") def findProvider(t: Type): Option[Provider] = providers.get(t) - - def go(t: Type): Tree = findProvider(t) match { - case Some(r: Resource) => r.ident - case Some(fm: FactoryMethod) => { - lazy val paramLists: List[List[Symbol]] = List(fm.params.map(_.symbol)) - - def wireParams(paramLists: List[List[Symbol]]): List[List[Tree]] = - paramLists.map(_.map(p => go(p.typeSignature))) - - val applyArgs: List[List[Tree]] =wireParams(paramLists) - - applyArgs.foldLeft(fm.value)((acc: Tree, args: List[Tree]) => Apply(acc, args)) - } - case None => c.abort(c.enclosingPosition, s"Cannot provide a value of type: [$t]") - } + def go(t: Type): Tree ={ + println(s"GO FOR [$t]") + // val dependencyResolver = new DependencyResolver2[c.type, Type, Tree](c, log)(resources.map(r => (r.resourceType, r.ident)).toMap)(t1 => if (counter.incrementAndGet() < 10) q"wireResourceRec[$t1](..$dependencies)" else c.abort(c.enclosingPosition, "KAPPA")) + val dependencyResolver = new DependencyResolver2[c.type, Type, Tree](c, log)(resources.map(r => (r.resourceType, r.ident)).toMap)(go(_)) + val companionCrimper = new CompanionCrimperTypeBased[c.type, Type](c, log, t) + val constructorCrimper = new ConstructorCrimperTypeBased[c.type, Type](c, log, t) + // val constructorCrimper = new ConstructorCrimper[c.type, T](c, log) + // val companionCrimper = new CompanionCrimper[c.type, T](c, log) + val r = (constructorCrimper.constructorTree(dependencyResolver) orElse companionCrimper.applyTree(dependencyResolver)) getOrElse + c.abort(c.enclosingPosition, "KAPPA") + + println(s"CONSTRUCTED [$r]") + r + } + // findProvider(t) match { + // case Some(r: Resource) => r.ident + // case Some(fm: FactoryMethod) => { + // lazy val paramLists: List[List[Symbol]] = List(fm.params.map(_.symbol)) + + // def wireParams(paramLists: List[List[Symbol]]): List[List[Tree]] = + // paramLists.map(_.map(p => go(p.typeSignature))) + + // val applyArgs: List[List[Tree]] =wireParams(paramLists) + + // applyArgs.foldLeft(fm.value)((acc: Tree, args: List[Tree]) => Apply(acc, args)) + // } + // case None => c.abort(c.enclosingPosition, s"Cannot provide a value of type: [$t]") + // } val generatedInstance = go(targetType.tpe) @@ -75,9 +89,27 @@ object MacwireCatsEffectMacros { q"${resource.value}.flatMap((${resource.ident}: ${resource.tpe}) => $acc)" } - log(s"CODE: [$code]") + log(s"\n\nCODE: [$code]\n\n") + println(s"\n\nCODE: [$code]\n\n") c.Expr[CatsResource[IO, T]](code) } + class DependencyResolver2[C <: blackbox.Context, TypeC <: C#Type, TreeC <: C#Tree](override val c: C, debug: Logger)(values: Map[TypeC, TreeC])(resolutionFallback: TypeC => TreeC) extends DependencyResolver[C, TypeC, TreeC](c, debug)(resolutionFallback) { + import c.universe._ + + override def resolve(param: Symbol, t: Type): Tree = { + println(s"LOOKING FOR PARAM [$param] TYPE [$t] in SET [${values.keySet.mkString(", ")}]") + // values(t.asInstanceOf[TypeC]).asInstanceOf[Tree] + log(s"LOOKING FOR PARAM [$param] TYPE [$t]") + values.get(t.asInstanceOf[TypeC]) match { + case Some(value) => + println(s"FOUND [$value] for [$t]") + value.asInstanceOf[Tree] + case None => + log(s"FALLBACK TRIGGERED FOR [$param]:[$t]") + resolutionFallback(t.asInstanceOf[TypeC]).asInstanceOf[Tree] + } + } + } } diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala index aebe9a98..debc6cf3 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala @@ -5,5 +5,6 @@ import cats.effect._ package object catseffectsupport { //hmmm at the beginning we are going to accept here only resources, but we expect to accept here also configs, factory methods, effects and so on. I'd like to express the current state of work with the accepted type as close as possible // Phase 2 - accept Resource[IO, ?] & factory methods + // Phase 3 - accept Resource[IO, ?] & instances, but factory methods & constructors def wireResourceRec[T](dependencies: Any*): Resource[IO, T] = macro MacwireCatsEffectMacros.wireResourceRec_impl[T] } \ No newline at end of file From f4ad58c05407fec85bc7f50cfe79ffaf3051b7cc Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Sat, 9 Oct 2021 09:57:21 +0200 Subject: [PATCH 06/32] Refactor crimpers --- .../internals/ConstructorCrimper.scala | 2 + .../macwire/CompanionCrimperTypeBased.scala | 59 +++-- .../macwire/ConstructorCrimperTypeBased.scala | 236 ++++++++++++------ .../MacwireCatsEffectMacros.scala | 124 ++++----- .../wireResourceRec-missing-deps.failure | 2 +- .../test-cases/wireResourceRec-nested.success | 15 ++ ...ireResourceRec-nestedWithCompanion.success | 18 ++ ...ireResourceRec-useEmptyConstructor.success | 12 + .../catseffectsupport/CompileTests.scala | 2 +- 9 files changed, 274 insertions(+), 196 deletions(-) create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nested.success create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nestedWithCompanion.success create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-useEmptyConstructor.success 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 d5224022..21314b87 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 @@ -15,6 +15,8 @@ private[macwire] class ConstructorCrimper[C <: blackbox.Context, T: C#WeakTypeTa // be directly instantiated lazy val targetTypeD: Type = targetType.dealias + lazy val classOfT: c.Expr[Class[T]] = c.Expr[Class[T]](q"classOf[$targetType]") + lazy val publicConstructors: Iterable[Symbol] = { val ctors = targetType.members .filter(m => m.isMethod && m.asMethod.isConstructor && m.isPublic) diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/CompanionCrimperTypeBased.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/CompanionCrimperTypeBased.scala index a0bbc975..d2aa3292 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/CompanionCrimperTypeBased.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/CompanionCrimperTypeBased.scala @@ -4,45 +4,44 @@ import com.softwaremill.macwire.internals._ import scala.reflect.macros.blackbox -class CompanionCrimperTypeBased [C <: blackbox.Context, TypeC <: C#Type](val c: C, log: Logger, tpe: TypeC) { - import c.universe._ +object CompanionCrimperTypeBased { + def applyTree[C <: blackbox.Context](c: C, log: Logger)(targetType: c.Type, resolver: c.Type => c.Tree): Option[c.Tree] = { + import c.universe._ - type DependencyResolverType = DependencyResolver[c.type, Type, Tree] + type Resolver = Type => Tree - lazy val targetType: Type = tpe.asInstanceOf[Type] + lazy val companionType: Option[Type] = if(targetType.companion == NoType) None else Some(targetType.companion) - lazy val companionType: Option[Type] = if(targetType.companion == NoType) None else Some(targetType.companion) + def isCompanionApply(method: Symbol): Boolean = + method.isMethod && + method.isPublic && + method.asMethod.returnType <:< targetType && + method.asMethod.name.decodedName.toString == "apply" - def isCompanionApply(method: Symbol): Boolean = - method.isMethod && - method.isPublic && - method.asMethod.returnType <:< targetType && - method.asMethod.name.decodedName.toString == "apply" + lazy val applies: Option[List[Symbol]] = log.withBlock(s"Looking for apply methods of Companion Object in [$companionType]") { + val as: Option[List[Symbol]] = companionType.map(_.members.filter(isCompanionApply).toList) + as.foreach(x => log.withBlock(s"There are ${x.size} apply methods:" ) { x.foreach(c => log(showApply(c))) }) + as + } - lazy val applies: Option[List[Symbol]] = log.withBlock("Looking for apply methods of Companion Object") { - val as: Option[List[Symbol]] = companionType.map(_.members.filter(isCompanionApply).toList) - as.foreach(x => log.withBlock(s"There are ${x.size} apply methods:" ) { x.foreach(c => log(showApply(c))) }) - as - } - - lazy val apply: Option[Symbol] = applies.flatMap( _ match { - case applyMethod :: Nil => Some(applyMethod) - case _ => None - }) + lazy val apply: Option[Symbol] = applies.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 applySelect: Option[Select] = apply.map(a => Select(Ident(targetType.typeSymbol.companion), a)) - lazy val applyParamLists: Option[List[List[Symbol]]] = apply.map(_.asMethod.paramLists) + lazy val applyParamLists: Option[List[List[Symbol]]] = apply.map(_.asMethod.paramLists) - def wireParams(dependencyResolver: DependencyResolverType)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map(p => dependencyResolver.resolve(p, p.typeSignature))) + def wireParams(resolver: Resolver)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map(p => resolver(p.typeSignature))) - def applyArgs(dependencyResolver: DependencyResolverType): Option[List[List[Tree]]] = applyParamLists.map(wireParams(dependencyResolver)) + def applyArgs(resolver: Resolver): Option[List[List[Tree]]] = applyParamLists.map(x => wireParams(resolver)(x)) + def showApply(c: Symbol): String = c.asMethod.typeSignature.toString - def applyTree(dependencyResolver: DependencyResolverType): Option[Tree] = for { - pl: List[List[Tree]] <- applyArgs(dependencyResolver) - applyMethod: Tree <- applySelect - } yield pl.foldLeft(applyMethod)((acc: Tree, args: List[Tree]) => Apply(acc, args)) + for { + pl: List[List[Tree]] <- applyArgs(resolver) + applyMethod: Tree <- applySelect + } yield pl.foldLeft(applyMethod)((acc: Tree, args: List[Tree]) => Apply(acc, args)) + } - def showApply(c: Symbol): String = c.asMethod.typeSignature.toString } - diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/ConstructorCrimperTypeBased.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/ConstructorCrimperTypeBased.scala index b02378d4..195eb78d 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/ConstructorCrimperTypeBased.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/ConstructorCrimperTypeBased.scala @@ -3,96 +3,168 @@ package com.softwaremill.macwire import scala.reflect.macros.blackbox import com.softwaremill.macwire.internals._ -class ConstructorCrimperTypeBased[C <: blackbox.Context, TypeC <: C#Type] (val c: C, log: Logger, tpe: TypeC) { - import c.universe._ - - type DependencyResolverType = DependencyResolver[c.type, Type, Tree] - - lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) - - lazy val targetType: Type = tpe.asInstanceOf[Type] - - // 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 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(c => log(showConstructor(c))) } - 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(c => log(showConstructor(c))) } - ctors - } +object ConstructorCrimperTypeBased { + def constructorTree[C <: blackbox.Context](c: C, log: Logger)(targetType: c.Type, resolver: c.Type => c.Tree): Option[c.Tree] = { + import c.universe._ + type Resolver = Type => Tree + lazy val targetTypeD: Type = targetType.dealias + + 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(c => log(showConstructor(c))) } + ctors + } - lazy val injectConstructor: Option[Symbol] = if(injectConstructors.size > 1) abort(s"Ambiguous constructors annotated with @javax.inject.Inject for type [$targetType]") else injectConstructors.headOption + lazy val primaryConstructor: Option[Symbol] = publicConstructors.find(_.asMethod.isPrimaryConstructor) - lazy val constructor: Option[Symbol] = log.withBlock(s"Looking for constructor for $targetType"){ - val ctor = injectConstructor orElse primaryConstructor - ctor.foreach(ctor => log(s"Found ${showConstructor(ctor)}")) - ctor - } + 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(c => log(showConstructor(c))) } + ctors + } - lazy val constructorParamLists: Option[List[List[Symbol]]] = constructor.map(_.asMethod.paramLists.filterNot(_.headOption.exists(_.isImplicit))) + lazy val injectConstructor: Option[Symbol] = if(injectConstructors.size > 1) abort(s"Ambiguous constructors annotated with @javax.inject.Inject for type [$targetType]") else injectConstructors.headOption - def constructorArgs(dependencyResolver: DependencyResolverType): Option[List[List[Tree]]] = log.withBlock("Looking for targetConstructor arguments") { - constructorParamLists.map(wireConstructorParams(dependencyResolver)) - } + lazy val constructor: Option[Symbol] = log.withBlock(s"Looking for constructor for $targetType"){ + val ctor = injectConstructor orElse primaryConstructor + ctor.foreach(ctor => log(s"Found ${showConstructor(ctor)}")) + ctor + } - def constructorArgsWithImplicitLookups(dependencyResolver: DependencyResolverType): Option[List[List[Tree]]] = log.withBlock("Looking for targetConstructor arguments with implicit lookups") { - constructor.map(_.asMethod.paramLists).map(wireConstructorParamsWithImplicitLookups(dependencyResolver)) - } + lazy val constructorParamLists: Option[List[List[Symbol]]] = constructor.map(_.asMethod.paramLists.filterNot(_.headOption.exists(_.isImplicit))) - def constructorTree(dependencyResolver: DependencyResolverType): Option[Tree] = log.withBlock(s"Creating Constructor Tree for $targetType"){ - val constructionMethodTree: Tree = Select(New(Ident(targetTypeD.typeSymbol)), termNames.CONSTRUCTOR) - constructorArgs(dependencyResolver).map(_.foldLeft(constructionMethodTree)((acc: Tree, args: List[Tree]) => Apply(acc, args))) - } + def constructorArgs(resolver: Resolver): Option[List[List[Tree]]] = log.withBlock("Looking for targetConstructor arguments") { + constructorParamLists.map(wireConstructorParams(resolver)(_)) + } - def wireConstructorParams(dependencyResolver: DependencyResolverType)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map(p => dependencyResolver.resolve(p, /*SI-4751*/paramType(p)))) + def wireConstructorParams(resolver: Resolver)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map(p => resolver(paramType(p)))) - def wireConstructorParamsWithImplicitLookups(dependencyResolver: DependencyResolverType)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map { - case i if i.isImplicit => q"implicitly[${paramType(i)}]" - case p => dependencyResolver.resolve(p, /*SI-4751*/ paramType(p)) - }) + def paramType(param: Symbol): Type = { + val (sym: Symbol, tpeArgs: List[Type]) = targetTypeD match { + case TypeRef(_, sym, tpeArgs) => (sym, tpeArgs) + case t => abort(s"Target type not supported for wiring: $t. Please file a bug report with your use-case.") + } + val pTpe = param.typeSignature.substituteTypes(sym.asClass.typeParams, tpeArgs) + if (param.asTerm.isByNameParam) pTpe.typeArgs.head else pTpe + } - private def paramType(param: Symbol): Type = { - val (sym: Symbol, tpeArgs: List[Type]) = targetTypeD match { - case TypeRef(_, sym, tpeArgs) => (sym, tpeArgs) - case t => abort(s"Target type not supported for wiring: $t. Please file a bug report with your use-case.") + /** + * 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$") + + def showConstructor(c: Symbol): String = c.asMethod.typeSignature.toString + + def abort(msg: String): Nothing = c.abort(c.enclosingPosition, msg) + + log.withBlock(s"Creating Constructor Tree for $targetType"){ + val constructionMethodTree: Tree = Select(New(Ident(targetTypeD.typeSymbol)), termNames.CONSTRUCTOR) + constructorArgs(resolver).map(_.foldLeft(constructionMethodTree)((acc: Tree, args: List[Tree]) => Apply(acc, args))) } - val pTpe = param.typeSignature.substituteTypes(sym.asClass.typeParams, tpeArgs) - if (param.asTerm.isByNameParam) pTpe.typeArgs.head else pTpe - } - - /** - * 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$") - - def showConstructor(c: Symbol): String = c.asMethod.typeSignature.toString - - def abort(msg: String): Nothing = c.abort(c.enclosingPosition, msg) + } } + +// class ConstructorCrimperTypeBased[C <: blackbox.Context, TypeC <: C#Type] (val c: C, log: Logger, tpe: TypeC) { + + +// lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) + +// lazy val targetType: Type = tpe.asInstanceOf[Type] + +// // 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 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(c => log(showConstructor(c))) } +// 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(c => log(showConstructor(c))) } +// ctors +// } + +// lazy val injectConstructor: Option[Symbol] = if(injectConstructors.size > 1) abort(s"Ambiguous constructors annotated with @javax.inject.Inject for type [$targetType]") else injectConstructors.headOption + +// lazy val constructor: Option[Symbol] = log.withBlock(s"Looking for constructor for $targetType"){ +// val ctor = injectConstructor orElse primaryConstructor +// ctor.foreach(ctor => log(s"Found ${showConstructor(ctor)}")) +// ctor +// } + +// lazy val constructorParamLists: Option[List[List[Symbol]]] = constructor.map(_.asMethod.paramLists.filterNot(_.headOption.exists(_.isImplicit))) + +// def constructorArgs(resolver: Resolver): Option[List[List[Tree]]] = log.withBlock("Looking for targetConstructor arguments") { +// constructorParamLists.map(wireConstructorParams(resolver)) +// } + +// def constructorArgsWithImplicitLookups(resolver: Resolver): Option[List[List[Tree]]] = log.withBlock("Looking for targetConstructor arguments with implicit lookups") { +// constructor.map(_.asMethod.paramLists).map(wireConstructorParamsWithImplicitLookups(resolver)) +// } + +// def wireConstructorParams(resolver: Resolver)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map(p => resolver(paramType(p)))) + +// def wireConstructorParamsWithImplicitLookups(resolver: Resolver)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map { +// case i if i.isImplicit => q"implicitly[${paramType(i)}]" +// case p => resolver(paramType(p)) +// }) + +// private def paramType(param: Symbol): Type = { +// val (sym: Symbol, tpeArgs: List[Type]) = targetTypeD match { +// case TypeRef(_, sym, tpeArgs) => (sym, tpeArgs) +// case t => abort(s"Target type not supported for wiring: $t. Please file a bug report with your use-case.") +// } +// val pTpe = param.typeSignature.substituteTypes(sym.asClass.typeParams, tpeArgs) +// if (param.asTerm.isByNameParam) pTpe.typeArgs.head else pTpe +// } + +// /** +// * 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$") + +// def showConstructor(c: Symbol): String = c.asMethod.typeSignature.toString + +// def abort(msg: String): Nothing = c.abort(c.enclosingPosition, msg) +// } diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala index e163d469..6b69dc52 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -4,112 +4,72 @@ package catseffectsupport import scala.reflect.macros.blackbox import cats.effect.{IO, Resource => CatsResource} import com.softwaremill.macwire.internals._ -import java.util.concurrent.atomic.AtomicLong -import cats.effect.kernel -import java.util.concurrent.atomic.AtomicInteger object MacwireCatsEffectMacros { - // type ComposableResource[T] = CatsResource[IO, T] private val log = new Logger() def wireResourceRec_impl[T: c.WeakTypeTag]( c: blackbox.Context )(dependencies: c.Expr[Any]*): c.Expr[CatsResource[IO, T]] = { import c.universe._ + val targetType = implicitly[c.WeakTypeTag[T]] lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) - val rit: c.WeakTypeTag[CatsResource[IO, T]] = implicitly - - sealed trait Provider //FIXME name.... - case class Resource(value: Tree) extends Provider { + case class Resource(value: Tree) { lazy val resourceType = typeCheckUtil.typeCheckIfNeeded(value).typeArgs(1) lazy val ident = Ident(TermName(c.freshName())) lazy val tpe = typeCheckUtil.typeCheckIfNeeded(value) } - case class FactoryMethod(value: Tree) extends Provider { - lazy val returnType: Type = value match { - case f: Function => f.body.tpe - } - lazy val params = value match { - case f: Function => f.vparams + val resources = dependencies + .map { expr => + val checkedType = typeCheckUtil.typeCheckIfNeeded(expr.tree) + + if (!checkedType.typeSymbol.fullName.startsWith("cats.effect.kernel.Resource")) + c.abort(c.enclosingPosition, s"Unsupported resource type [$checkedType].") + else if (checkedType.typeArgs.size != 2) + c.abort(c.enclosingPosition, s"Expected 2 type args, but found [${checkedType.typeArgs.size}].") + else Resource(expr.tree) } + .map(r => (r.resourceType, r)) + .toMap + + log(s"RESOURCES: [${resources.mkString(", ")}]") + + def findResource(t: Type): Option[Resource] = resources.get(t) + + def isWireable(tpe: Type): Boolean = { + val name = tpe.typeSymbol.fullName + + !name.startsWith("java.lang.") && !name.startsWith("scala.") } - val (r, fm) = dependencies.partition(dep => - typeCheckUtil - .typeCheckIfNeeded(dep.tree) - .typeConstructor =:= rit.tpe.typeConstructor //FIXME there is a better way to check resource type, I believe + we need to check if `factoryMethods` really contains factory methods - ) - - val resources = r.map(expr => Resource(expr.tree)) - val factoryMethods = fm.map(expr => FactoryMethod(expr.tree)) - - log(s"RESOURCE: [${resources.mkString(", ")}]") - log(s"FACTORY_METHODS: [${factoryMethods.mkString(", ")}]") - - val providers: Map[Type, Provider] = - resources.map(r => (r.resourceType, r)).toMap ++ factoryMethods.map(fm => (fm.returnType, fm)).toMap - val counter = new AtomicInteger(0) - log(s"PROVIDERS: [${providers.mkString(", ")}]") - def findProvider(t: Type): Option[Provider] = providers.get(t) - def go(t: Type): Tree ={ - println(s"GO FOR [$t]") - // val dependencyResolver = new DependencyResolver2[c.type, Type, Tree](c, log)(resources.map(r => (r.resourceType, r.ident)).toMap)(t1 => if (counter.incrementAndGet() < 10) q"wireResourceRec[$t1](..$dependencies)" else c.abort(c.enclosingPosition, "KAPPA")) - val dependencyResolver = new DependencyResolver2[c.type, Type, Tree](c, log)(resources.map(r => (r.resourceType, r.ident)).toMap)(go(_)) - val companionCrimper = new CompanionCrimperTypeBased[c.type, Type](c, log, t) - val constructorCrimper = new ConstructorCrimperTypeBased[c.type, Type](c, log, t) - // val constructorCrimper = new ConstructorCrimper[c.type, T](c, log) - // val companionCrimper = new CompanionCrimper[c.type, T](c, log) - val r = (constructorCrimper.constructorTree(dependencyResolver) orElse companionCrimper.applyTree(dependencyResolver)) getOrElse - c.abort(c.enclosingPosition, "KAPPA") - + lazy val resolutionFallback: c.Type => c.Tree = tpe => + if (isWireable(tpe)) findResource(tpe).map(_.ident).getOrElse(go(tpe)) + else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") + + def go(t: Type): Tree = { + + val r = + (ConstructorCrimperTypeBased.constructorTree(c, log)(t, resolutionFallback) orElse CompanionCrimperTypeBased + .applyTree(c, log)(t, resolutionFallback)) getOrElse + c.abort(c.enclosingPosition, s"Failed for [$t]") + println(s"CONSTRUCTED [$r]") r - } - // findProvider(t) match { - // case Some(r: Resource) => r.ident - // case Some(fm: FactoryMethod) => { - // lazy val paramLists: List[List[Symbol]] = List(fm.params.map(_.symbol)) - - // def wireParams(paramLists: List[List[Symbol]]): List[List[Tree]] = - // paramLists.map(_.map(p => go(p.typeSignature))) - - // val applyArgs: List[List[Tree]] =wireParams(paramLists) - - // applyArgs.foldLeft(fm.value)((acc: Tree, args: List[Tree]) => Apply(acc, args)) - // } - // case None => c.abort(c.enclosingPosition, s"Cannot provide a value of type: [$t]") - // } - + } + val generatedInstance = go(targetType.tpe) - val code = resources.foldRight(q"cats.effect.Resource.pure[cats.effect.IO, $targetType]($generatedInstance)") { case (resource, acc) => - - q"${resource.value}.flatMap((${resource.ident}: ${resource.tpe}) => $acc)" - } - log(s"\n\nCODE: [$code]\n\n") - println(s"\n\nCODE: [$code]\n\n") - c.Expr[CatsResource[IO, T]](code) + 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)" + } + log(s"CODE: [$code]") + c.Expr[CatsResource[IO, T]](code) } - class DependencyResolver2[C <: blackbox.Context, TypeC <: C#Type, TreeC <: C#Tree](override val c: C, debug: Logger)(values: Map[TypeC, TreeC])(resolutionFallback: TypeC => TreeC) extends DependencyResolver[C, TypeC, TreeC](c, debug)(resolutionFallback) { - import c.universe._ - - override def resolve(param: Symbol, t: Type): Tree = { - println(s"LOOKING FOR PARAM [$param] TYPE [$t] in SET [${values.keySet.mkString(", ")}]") - // values(t.asInstanceOf[TypeC]).asInstanceOf[Tree] - log(s"LOOKING FOR PARAM [$param] TYPE [$t]") - values.get(t.asInstanceOf[TypeC]) match { - case Some(value) => - println(s"FOUND [$value] for [$t]") - value.asInstanceOf[Tree] - case None => - log(s"FALLBACK TRIGGERED FOR [$param]:[$t]") - resolutionFallback(t.asInstanceOf[TypeC]).asInstanceOf[Tree] - } - } - } } diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure index 79a2c8a5..e143e1e1 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure @@ -1,7 +1,7 @@ import cats.effect._ class DatabaseAccess() -class SecurityFilter() +class SecurityFilter(s: String) class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) object UserModule { diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nested.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nested.success new file mode 100644 index 00000000..786fd035 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nested.success @@ -0,0 +1,15 @@ +import cats.effect._ + +class DatabaseAccess() +class SecurityFilter(databaseAccess: DatabaseAccess) +class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) +class UserStatusReader(databaseAccess: DatabaseAccess, userFinder: UserFinder) + +object UserModule { + import com.softwaremill.macwire.catseffectsupport._ + + val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) + + val theUserStatusReader: Resource[IO, UserStatusReader] = wireResourceRec[UserStatusReader](theDatabaseAccess) + +} \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nestedWithCompanion.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nestedWithCompanion.success new file mode 100644 index 00000000..af0d1df9 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nestedWithCompanion.success @@ -0,0 +1,18 @@ +import cats.effect._ + +class DatabaseAccess() +class SecurityFilter private (databaseAccess: DatabaseAccess) +object SecurityFilter { + def apply(databaseAccess: DatabaseAccess): SecurityFilter = new SecurityFilter(databaseAccess) +} + +class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) +class UserStatusReader(databaseAccess: DatabaseAccess, userFinder: UserFinder) + +object UserModule { + import com.softwaremill.macwire.catseffectsupport._ + + val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) + + val theUserStatusReader: Resource[IO, UserStatusReader] = wireResourceRec[UserStatusReader](theDatabaseAccess) +} \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-useEmptyConstructor.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-useEmptyConstructor.success new file mode 100644 index 00000000..79a2c8a5 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-useEmptyConstructor.success @@ -0,0 +1,12 @@ +import cats.effect._ + +class DatabaseAccess() +class SecurityFilter() +class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) + +object UserModule { + import com.softwaremill.macwire.catseffectsupport._ + + val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) + val theUserFinder: Resource[IO, UserFinder] = wireResourceRec[UserFinder](theDatabaseAccess) +} \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala index 616eed3c..a9496ed0 100644 --- a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala +++ b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala @@ -6,7 +6,7 @@ class CompileTests extends CompileTestsSupport { runTestsWith( expectedFailures = List( - "wireResourceRec-missing-deps" -> List("Cannot find a value of type: [SecurityFilter]") + "wireResourceRec-missing-deps" -> List("Cannot find a value of type: [String]") ), expectedWarnings = List() ) From 9434160933259dfdc5e2679b7412a13d540928c9 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Mon, 11 Oct 2021 20:42:13 +0200 Subject: [PATCH 07/32] Fix test --- ...wireResourceRec-nestedWithCompanion.success | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nestedWithCompanion.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nestedWithCompanion.success index af0d1df9..a8b360b5 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nestedWithCompanion.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nestedWithCompanion.success @@ -1,17 +1,17 @@ import cats.effect._ -class DatabaseAccess() -class SecurityFilter private (databaseAccess: DatabaseAccess) -object SecurityFilter { - def apply(databaseAccess: DatabaseAccess): SecurityFilter = new SecurityFilter(databaseAccess) -} - -class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) -class UserStatusReader(databaseAccess: DatabaseAccess, userFinder: UserFinder) - object UserModule { import com.softwaremill.macwire.catseffectsupport._ + class DatabaseAccess() + class SecurityFilter private (databaseAccess: DatabaseAccess) + object SecurityFilter { + def apply(databaseAccess: DatabaseAccess): SecurityFilter = new SecurityFilter(databaseAccess) + } + + class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) + class UserStatusReader(databaseAccess: DatabaseAccess, userFinder: UserFinder) + val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) val theUserStatusReader: Resource[IO, UserStatusReader] = wireResourceRec[UserStatusReader](theDatabaseAccess) From 26bfca2d0d954b52a7b2c12d71fd1c43e47e5989 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Mon, 11 Oct 2021 20:56:31 +0200 Subject: [PATCH 08/32] Rename method --- .../macwire/catseffectsupport/MacwireCatsEffectMacros.scala | 2 +- .../com/softwaremill/macwire/catseffectsupport/package.scala | 5 +---- .../{wireResourceRec-flat.success => wireApp-flat.success} | 2 +- ...Rec-missing-deps.failure => wireApp-missing-deps.failure} | 2 +- ...wireResourceRec-nested.success => wireApp-nested.success} | 2 +- ...Companion.success => wireApp-nestedWithCompanion.success} | 2 +- ...nstructor.success => wireApp-useEmptyConstructor.success} | 2 +- .../macwire/catseffectsupport/CompileTests.scala | 2 +- 8 files changed, 8 insertions(+), 11 deletions(-) rename macrosCatsEffectTests/src/test/resources/test-cases/{wireResourceRec-flat.success => wireApp-flat.success} (78%) rename macrosCatsEffectTests/src/test/resources/test-cases/{wireResourceRec-missing-deps.failure => wireApp-missing-deps.failure} (77%) rename macrosCatsEffectTests/src/test/resources/test-cases/{wireResourceRec-nested.success => wireApp-nested.success} (90%) rename macrosCatsEffectTests/src/test/resources/test-cases/{wireResourceRec-nestedWithCompanion.success => wireApp-nestedWithCompanion.success} (92%) rename macrosCatsEffectTests/src/test/resources/test-cases/{wireResourceRec-useEmptyConstructor.success => wireApp-useEmptyConstructor.success} (76%) diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala index 6b69dc52..45b87d3c 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -8,7 +8,7 @@ import com.softwaremill.macwire.internals._ object MacwireCatsEffectMacros { private val log = new Logger() - def wireResourceRec_impl[T: c.WeakTypeTag]( + def wireApp_impl[T: c.WeakTypeTag]( c: blackbox.Context )(dependencies: c.Expr[Any]*): c.Expr[CatsResource[IO, T]] = { import c.universe._ diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala index debc6cf3..3e5fc181 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala @@ -3,8 +3,5 @@ package com.softwaremill.macwire import cats.effect._ package object catseffectsupport { - //hmmm at the beginning we are going to accept here only resources, but we expect to accept here also configs, factory methods, effects and so on. I'd like to express the current state of work with the accepted type as close as possible - // Phase 2 - accept Resource[IO, ?] & factory methods - // Phase 3 - accept Resource[IO, ?] & instances, but factory methods & constructors - def wireResourceRec[T](dependencies: Any*): Resource[IO, T] = macro MacwireCatsEffectMacros.wireResourceRec_impl[T] + def wireApp[T](dependencies: Any*): Resource[IO, T] = macro MacwireCatsEffectMacros.wireApp_impl[T] } \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-flat.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-flat.success similarity index 78% rename from macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-flat.success rename to macrosCatsEffectTests/src/test/resources/test-cases/wireApp-flat.success index ed41f000..9e2f41f9 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-flat.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-flat.success @@ -9,5 +9,5 @@ object UserModule { val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) val theSecurityFilter: Resource[IO, SecurityFilter] = Resource.pure(new SecurityFilter()) - val theUserFinder: Resource[IO, UserFinder] = wireResourceRec[UserFinder](theDatabaseAccess, theSecurityFilter) + val theUserFinder: Resource[IO, UserFinder] = wireApp[UserFinder](theDatabaseAccess, theSecurityFilter) } \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-missing-deps.failure similarity index 77% rename from macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure rename to macrosCatsEffectTests/src/test/resources/test-cases/wireApp-missing-deps.failure index e143e1e1..5ecd5c55 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-missing-deps.failure +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-missing-deps.failure @@ -8,5 +8,5 @@ object UserModule { import com.softwaremill.macwire.catseffectsupport._ val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) - val theUserFinder: Resource[IO, UserFinder] = wireResourceRec[UserFinder](theDatabaseAccess) + val theUserFinder: Resource[IO, UserFinder] = wireApp[UserFinder](theDatabaseAccess) } \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nested.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nested.success similarity index 90% rename from macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nested.success rename to macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nested.success index 786fd035..d6635530 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nested.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nested.success @@ -10,6 +10,6 @@ object UserModule { val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) - val theUserStatusReader: Resource[IO, UserStatusReader] = wireResourceRec[UserStatusReader](theDatabaseAccess) + val theUserStatusReader: Resource[IO, UserStatusReader] = wireApp[UserStatusReader](theDatabaseAccess) } \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nestedWithCompanion.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nestedWithCompanion.success similarity index 92% rename from macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nestedWithCompanion.success rename to macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nestedWithCompanion.success index a8b360b5..bdcfc5d1 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-nestedWithCompanion.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nestedWithCompanion.success @@ -14,5 +14,5 @@ object UserModule { val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) - val theUserStatusReader: Resource[IO, UserStatusReader] = wireResourceRec[UserStatusReader](theDatabaseAccess) + val theUserStatusReader: Resource[IO, UserStatusReader] = wireApp[UserStatusReader](theDatabaseAccess) } \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-useEmptyConstructor.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-useEmptyConstructor.success similarity index 76% rename from macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-useEmptyConstructor.success rename to macrosCatsEffectTests/src/test/resources/test-cases/wireApp-useEmptyConstructor.success index 79a2c8a5..33a84527 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireResourceRec-useEmptyConstructor.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-useEmptyConstructor.success @@ -8,5 +8,5 @@ object UserModule { import com.softwaremill.macwire.catseffectsupport._ val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) - val theUserFinder: Resource[IO, UserFinder] = wireResourceRec[UserFinder](theDatabaseAccess) + val theUserFinder: Resource[IO, UserFinder] = wireApp[UserFinder](theDatabaseAccess) } \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala index a9496ed0..61e0df37 100644 --- a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala +++ b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala @@ -6,7 +6,7 @@ class CompileTests extends CompileTestsSupport { runTestsWith( expectedFailures = List( - "wireResourceRec-missing-deps" -> List("Cannot find a value of type: [String]") + "wireApp-missing-deps" -> List("Cannot find a value of type: [String]") ), expectedWarnings = List() ) From 79f752c2861dc0249bd96c3dc87eb128deb3dc9b Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Mon, 11 Oct 2021 21:34:17 +0200 Subject: [PATCH 09/32] Apply CompanionCrimper changes --- .../macwire/internals/CompanionCrimper.scala | 57 ++++++++++++------- .../macwire/CompanionCrimperTypeBased.scala | 47 --------------- .../MacwireCatsEffectMacros.scala | 8 ++- 3 files changed, 43 insertions(+), 69 deletions(-) delete mode 100644 macrosCatsEffect/src/main/scala/com/softwaremill/macwire/CompanionCrimperTypeBased.scala 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 6f9eb7f8..4ec6a2ab 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 @@ -2,45 +2,62 @@ package com.softwaremill.macwire.internals import scala.reflect.macros.blackbox -private[macwire] class CompanionCrimper [C <: blackbox.Context, T: C#WeakTypeTag](val c: C, log: Logger) { +private[macwire] class CompanionCrimper[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 - lazy val companionType: Option[Type] = if(targetType.companion == NoType) None else Some(targetType.companion) + lazy val companionType: Option[Type] = CompanionCrimper.companionType(c)(targetType) - def isCompanionApply(method: Symbol): Boolean = + lazy val applies: Option[List[Symbol]] = CompanionCrimper.applies(c, log)(targetType) + + def applyTree(dependencyResolver: DependencyResolverType): Option[Tree] = CompanionCrimper.applyTree[C](c, log)(targetType, dependencyResolver.resolve(_, _)) + +} + +object CompanionCrimper { + private def showApply[C <: blackbox.Context](c: C)(s: c.Symbol): String = s.asMethod.typeSignature.toString + + private def isCompanionApply[C <: blackbox.Context](c: C)(targetType: c.Type, method: c.Symbol): Boolean = method.isMethod && method.isPublic && method.asMethod.returnType <:< targetType && method.asMethod.name.decodedName.toString == "apply" - lazy val applies: Option[List[Symbol]] = log.withBlock("Looking for apply methods of Companion Object") { - val as: Option[List[Symbol]] = companionType.map(_.members.filter(isCompanionApply).toList) - as.foreach(x => log.withBlock(s"There are ${x.size} apply methods:" ) { x.foreach(c => log(showApply(c))) }) + private def companionType[C <: blackbox.Context](c: C)(targetType: c.Type): Option[c.Type] = { + import c.universe._ + + if(targetType.companion == NoType) None else Some(targetType.companion) + } + + private def applies[C <: blackbox.Context](c: C, log: Logger)(targetType: c.Type): Option[List[c.Symbol]] = log.withBlock("Looking for apply methods of Companion Object") { + val as: Option[List[c.Symbol]] = companionType(c)(targetType).map(_.members.filter(CompanionCrimper.isCompanionApply(c)(targetType, _)).toList) + as.foreach(x => log.withBlock(s"There are ${x.size} apply methods:" ) { x.foreach(s => log(showApply(c)(s))) }) as } - lazy val apply: Option[Symbol] = applies.flatMap( _ match { - case applyMethod :: Nil => Some(applyMethod) - case _ => None - }) + def applyTree[C <: blackbox.Context](c: C, log: Logger)(targetType: c.Type, resolver: (c.Symbol, c.Type) => c.Tree): Option[c.Tree] = { + import c.universe._ - lazy val applySelect: Option[Select] = apply.map(a => Select(Ident(targetType.typeSymbol.companion), a)) + lazy val apply: Option[Symbol] = CompanionCrimper.applies(c, log)(targetType).flatMap( _ match { + case applyMethod :: Nil => Some(applyMethod) + case _ => None + }) - lazy val applyParamLists: Option[List[List[Symbol]]] = apply.map(_.asMethod.paramLists) + lazy val applySelect: Option[Select] = apply.map(a => Select(Ident(targetType.typeSymbol.companion), a)) - def wireParams(dependencyResolver: DependencyResolverType)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map(p => dependencyResolver.resolve(p, p.typeSignature))) + lazy val applyParamLists: Option[List[List[Symbol]]] = apply.map(_.asMethod.paramLists) - def applyArgs(dependencyResolver: DependencyResolverType): Option[List[List[Tree]]] = applyParamLists.map(wireParams(dependencyResolver)) + def wireParams(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map(p => resolver(p, p.typeSignature))) - def applyTree(dependencyResolver: DependencyResolverType): Option[Tree] = for { - pl: List[List[Tree]] <- applyArgs(dependencyResolver) - applyMethod: Tree <- applySelect - } yield pl.foldLeft(applyMethod)((acc: Tree, args: List[Tree]) => Apply(acc, args)) + def applyArgs: Option[List[List[Tree]]] = applyParamLists.map(x => wireParams(x)) - def showApply(c: Symbol): String = c.asMethod.typeSignature.toString -} + for { + pl: List[List[Tree]] <- applyArgs + applyMethod: Tree <- applySelect + } yield pl.foldLeft(applyMethod)((acc: Tree, args: List[Tree]) => Apply(acc, args)) + } +} diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/CompanionCrimperTypeBased.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/CompanionCrimperTypeBased.scala deleted file mode 100644 index d2aa3292..00000000 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/CompanionCrimperTypeBased.scala +++ /dev/null @@ -1,47 +0,0 @@ -package com.softwaremill.macwire - -import com.softwaremill.macwire.internals._ - -import scala.reflect.macros.blackbox - -object CompanionCrimperTypeBased { - def applyTree[C <: blackbox.Context](c: C, log: Logger)(targetType: c.Type, resolver: c.Type => c.Tree): Option[c.Tree] = { - import c.universe._ - - type Resolver = Type => Tree - - lazy val companionType: Option[Type] = if(targetType.companion == NoType) None else Some(targetType.companion) - - def isCompanionApply(method: Symbol): Boolean = - method.isMethod && - method.isPublic && - method.asMethod.returnType <:< targetType && - method.asMethod.name.decodedName.toString == "apply" - - lazy val applies: Option[List[Symbol]] = log.withBlock(s"Looking for apply methods of Companion Object in [$companionType]") { - val as: Option[List[Symbol]] = companionType.map(_.members.filter(isCompanionApply).toList) - as.foreach(x => log.withBlock(s"There are ${x.size} apply methods:" ) { x.foreach(c => log(showApply(c))) }) - as - } - - lazy val apply: Option[Symbol] = applies.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(resolver: Resolver)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map(p => resolver(p.typeSignature))) - - def applyArgs(resolver: Resolver): Option[List[List[Tree]]] = applyParamLists.map(x => wireParams(resolver)(x)) - def showApply(c: Symbol): String = c.asMethod.typeSignature.toString - - for { - pl: List[List[Tree]] <- applyArgs(resolver) - applyMethod: Tree <- applySelect - } yield pl.foldLeft(applyMethod)((acc: Tree, args: List[Tree]) => Apply(acc, args)) - } - -} diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala index 45b87d3c..08cc3025 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -45,14 +45,18 @@ object MacwireCatsEffectMacros { !name.startsWith("java.lang.") && !name.startsWith("scala.") } - lazy val resolutionFallback: c.Type => c.Tree = tpe => + lazy val resolutionFallback: (c.Symbol, c.Type) => c.Tree = (_, tpe) => + if (isWireable(tpe)) findResource(tpe).map(_.ident).getOrElse(go(tpe)) + else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") + + lazy val resolutionFallback2: c.Type => c.Tree = tpe => if (isWireable(tpe)) findResource(tpe).map(_.ident).getOrElse(go(tpe)) else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") def go(t: Type): Tree = { val r = - (ConstructorCrimperTypeBased.constructorTree(c, log)(t, resolutionFallback) orElse CompanionCrimperTypeBased + (ConstructorCrimperTypeBased.constructorTree(c, log)(t, resolutionFallback2) orElse CompanionCrimper .applyTree(c, log)(t, resolutionFallback)) getOrElse c.abort(c.enclosingPosition, s"Failed for [$t]") From a038fb833ce9edddd9959ece959d076bb152d502 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Mon, 11 Oct 2021 22:18:50 +0200 Subject: [PATCH 10/32] Apply ConstructorCrimper changes --- .../internals/ConstructorCrimper.scala | 171 +++++++++++------- .../macwire/ConstructorCrimperTypeBased.scala | 170 ----------------- .../MacwireCatsEffectMacros.scala | 10 +- 3 files changed, 109 insertions(+), 242 deletions(-) delete mode 100644 macrosCatsEffect/src/main/scala/com/softwaremill/macwire/ConstructorCrimperTypeBased.scala 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 21314b87..6aea957d 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 @@ -2,98 +2,139 @@ 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) { +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 typeCheckUtil = new TypeCheckUtil[c.type](c, log) - 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 classOfT: c.Expr[Class[T]] = c.Expr[Class[T]](q"classOf[$targetType]") + lazy val constructor: Option[Symbol] = ConstructorCrimper.constructor(c, log)(targetType) - 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(c => log(showConstructor(c))) } - ctors - } + def constructorArgsWithImplicitLookups(dependencyResolver: DependencyResolverType): Option[List[List[Tree]]] = + log.withBlock("Looking for targetConstructor arguments with implicit lookups") { + constructor.map(_.asMethod.paramLists).map(wireConstructorParamsWithImplicitLookups(dependencyResolver)) + } - lazy val primaryConstructor: Option[Symbol] = publicConstructors.find(_.asMethod.isPrimaryConstructor) + def constructorTree(dependencyResolver: DependencyResolverType): Option[Tree] = + ConstructorCrimper.constructorTree(c, log)(targetType, dependencyResolver.resolve(_, _)) - 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(c => log(showConstructor(c))) } - ctors - } + def wireConstructorParams( + dependencyResolver: DependencyResolverType + )(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map( + _.map(p => dependencyResolver.resolve(p, /*SI-4751*/ ConstructorCrimper.paramType(c)(targetTypeD, p))) + ) - lazy val injectConstructor: Option[Symbol] = if(injectConstructors.size > 1) abort(s"Ambiguous constructors annotated with @javax.inject.Inject for type [$targetType]") else injectConstructors.headOption + def wireConstructorParamsWithImplicitLookups( + dependencyResolver: DependencyResolverType + )(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map { + case i if i.isImplicit => q"implicitly[${ConstructorCrimper.paramType(c)(targetType, i)}]" + case p => dependencyResolver.resolve(p, /*SI-4751*/ ConstructorCrimper.paramType(c)(targetTypeD, p)) + }) - lazy val constructor: Option[Symbol] = log.withBlock(s"Looking for constructor for $targetType"){ - val ctor = injectConstructor orElse primaryConstructor - ctor.foreach(ctor => log(s"Found ${showConstructor(ctor)}")) - ctor - } +} - lazy val constructorParamLists: Option[List[List[Symbol]]] = constructor.map(_.asMethod.paramLists.filterNot(_.headOption.exists(_.isImplicit))) +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 + } - def constructorArgs(dependencyResolver: DependencyResolverType): Option[List[List[Tree]]] = log.withBlock("Looking for targetConstructor arguments") { - constructorParamLists.map(wireConstructorParams(dependencyResolver)) - } + lazy val primaryConstructor: Option[Symbol] = publicConstructors.find(_.asMethod.isPrimaryConstructor) - def constructorArgsWithImplicitLookups(dependencyResolver: DependencyResolverType): Option[List[List[Tree]]] = log.withBlock("Looking for targetConstructor arguments with implicit lookups") { - constructor.map(_.asMethod.paramLists).map(wireConstructorParamsWithImplicitLookups(dependencyResolver)) - } + 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 + } - def constructorTree(dependencyResolver: DependencyResolverType): Option[Tree] = log.withBlock(s"Creating Constructor Tree for $targetType"){ - val constructionMethodTree: Tree = Select(New(Ident(targetTypeD.typeSymbol)), termNames.CONSTRUCTOR) - constructorArgs(dependencyResolver).map(_.foldLeft(constructionMethodTree)((acc: Tree, args: List[Tree]) => Apply(acc, args))) + 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 wireConstructorParams(dependencyResolver: DependencyResolverType)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map(p => dependencyResolver.resolve(p, /*SI-4751*/paramType(p)))) + private def paramType[C <: blackbox.Context](c: C)(targetTypeD: c.Type, param: c.Symbol): c.Type = { + import c.universe._ - def wireConstructorParamsWithImplicitLookups(dependencyResolver: DependencyResolverType)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map { - case i if i.isImplicit => q"implicitly[${paramType(i)}]" - case p => dependencyResolver.resolve(p, /*SI-4751*/ paramType(p)) - }) - - private def paramType(param: Symbol): Type = { val (sym: Symbol, tpeArgs: List[Type]) = targetTypeD match { case TypeRef(_, sym, tpeArgs) => (sym, tpeArgs) - case t => abort(s"Target type not supported for wiring: $t. Please file a bug report with your use-case.") + case t => + c.abort( + c.enclosingPosition, + s"Target type not supported for wiring: $t. Please file a bug report with your use-case." + ) } val pTpe = param.typeSignature.substituteTypes(sym.asClass.typeParams, tpeArgs) if (param.asTerm.isByNameParam) pTpe.typeArgs.head else pTpe } - /** - * 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$") - - def showConstructor(c: Symbol): String = c.asMethod.typeSignature.toString - - def abort(msg: String): Nothing = c.abort(c.enclosingPosition, msg) + def constructorTree[C <: blackbox.Context]( + c: C, + log: Logger + )(targetType: c.Type, resolver: (c.Symbol, c.Type) => 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[Tree]]] = log.withBlock("Looking for targetConstructor arguments") { + constructorParamLists.map(wireConstructorParams(_)) + } + + def wireConstructorParams(paramLists: List[List[Symbol]]): List[List[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) + constructorArgs.map(_.foldLeft(constructionMethodTree)((acc: Tree, args: List[Tree]) => Apply(acc, args))) + } + } } diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/ConstructorCrimperTypeBased.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/ConstructorCrimperTypeBased.scala deleted file mode 100644 index 195eb78d..00000000 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/ConstructorCrimperTypeBased.scala +++ /dev/null @@ -1,170 +0,0 @@ -package com.softwaremill.macwire - -import scala.reflect.macros.blackbox -import com.softwaremill.macwire.internals._ - -object ConstructorCrimperTypeBased { - def constructorTree[C <: blackbox.Context](c: C, log: Logger)(targetType: c.Type, resolver: c.Type => c.Tree): Option[c.Tree] = { - import c.universe._ - type Resolver = Type => Tree - lazy val targetTypeD: Type = targetType.dealias - - 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(c => log(showConstructor(c))) } - 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(c => log(showConstructor(c))) } - ctors - } - - lazy val injectConstructor: Option[Symbol] = if(injectConstructors.size > 1) abort(s"Ambiguous constructors annotated with @javax.inject.Inject for type [$targetType]") else injectConstructors.headOption - - lazy val constructor: Option[Symbol] = log.withBlock(s"Looking for constructor for $targetType"){ - val ctor = injectConstructor orElse primaryConstructor - ctor.foreach(ctor => log(s"Found ${showConstructor(ctor)}")) - ctor - } - - lazy val constructorParamLists: Option[List[List[Symbol]]] = constructor.map(_.asMethod.paramLists.filterNot(_.headOption.exists(_.isImplicit))) - - def constructorArgs(resolver: Resolver): Option[List[List[Tree]]] = log.withBlock("Looking for targetConstructor arguments") { - constructorParamLists.map(wireConstructorParams(resolver)(_)) - } - - def wireConstructorParams(resolver: Resolver)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map(p => resolver(paramType(p)))) - - def paramType(param: Symbol): Type = { - val (sym: Symbol, tpeArgs: List[Type]) = targetTypeD match { - case TypeRef(_, sym, tpeArgs) => (sym, tpeArgs) - case t => abort(s"Target type not supported for wiring: $t. Please file a bug report with your use-case.") - } - val pTpe = param.typeSignature.substituteTypes(sym.asClass.typeParams, tpeArgs) - if (param.asTerm.isByNameParam) pTpe.typeArgs.head else pTpe - } - - /** - * 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$") - - def showConstructor(c: Symbol): String = c.asMethod.typeSignature.toString - - def abort(msg: String): Nothing = c.abort(c.enclosingPosition, msg) - - log.withBlock(s"Creating Constructor Tree for $targetType"){ - val constructionMethodTree: Tree = Select(New(Ident(targetTypeD.typeSymbol)), termNames.CONSTRUCTOR) - constructorArgs(resolver).map(_.foldLeft(constructionMethodTree)((acc: Tree, args: List[Tree]) => Apply(acc, args))) - } - } -} - -// class ConstructorCrimperTypeBased[C <: blackbox.Context, TypeC <: C#Type] (val c: C, log: Logger, tpe: TypeC) { - - -// lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) - -// lazy val targetType: Type = tpe.asInstanceOf[Type] - -// // 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 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(c => log(showConstructor(c))) } -// 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(c => log(showConstructor(c))) } -// ctors -// } - -// lazy val injectConstructor: Option[Symbol] = if(injectConstructors.size > 1) abort(s"Ambiguous constructors annotated with @javax.inject.Inject for type [$targetType]") else injectConstructors.headOption - -// lazy val constructor: Option[Symbol] = log.withBlock(s"Looking for constructor for $targetType"){ -// val ctor = injectConstructor orElse primaryConstructor -// ctor.foreach(ctor => log(s"Found ${showConstructor(ctor)}")) -// ctor -// } - -// lazy val constructorParamLists: Option[List[List[Symbol]]] = constructor.map(_.asMethod.paramLists.filterNot(_.headOption.exists(_.isImplicit))) - -// def constructorArgs(resolver: Resolver): Option[List[List[Tree]]] = log.withBlock("Looking for targetConstructor arguments") { -// constructorParamLists.map(wireConstructorParams(resolver)) -// } - -// def constructorArgsWithImplicitLookups(resolver: Resolver): Option[List[List[Tree]]] = log.withBlock("Looking for targetConstructor arguments with implicit lookups") { -// constructor.map(_.asMethod.paramLists).map(wireConstructorParamsWithImplicitLookups(resolver)) -// } - -// def wireConstructorParams(resolver: Resolver)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map(p => resolver(paramType(p)))) - -// def wireConstructorParamsWithImplicitLookups(resolver: Resolver)(paramLists: List[List[Symbol]]): List[List[Tree]] = paramLists.map(_.map { -// case i if i.isImplicit => q"implicitly[${paramType(i)}]" -// case p => resolver(paramType(p)) -// }) - -// private def paramType(param: Symbol): Type = { -// val (sym: Symbol, tpeArgs: List[Type]) = targetTypeD match { -// case TypeRef(_, sym, tpeArgs) => (sym, tpeArgs) -// case t => abort(s"Target type not supported for wiring: $t. Please file a bug report with your use-case.") -// } -// val pTpe = param.typeSignature.substituteTypes(sym.asClass.typeParams, tpeArgs) -// if (param.asTerm.isByNameParam) pTpe.typeArgs.head else pTpe -// } - -// /** -// * 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$") - -// def showConstructor(c: Symbol): String = c.asMethod.typeSignature.toString - -// def abort(msg: String): Nothing = c.abort(c.enclosingPosition, msg) -// } diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala index 08cc3025..5cf1ca4e 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -49,18 +49,14 @@ object MacwireCatsEffectMacros { if (isWireable(tpe)) findResource(tpe).map(_.ident).getOrElse(go(tpe)) else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") - lazy val resolutionFallback2: c.Type => c.Tree = tpe => - if (isWireable(tpe)) findResource(tpe).map(_.ident).getOrElse(go(tpe)) - else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") - def go(t: Type): Tree = { val r = - (ConstructorCrimperTypeBased.constructorTree(c, log)(t, resolutionFallback2) orElse CompanionCrimper + (ConstructorCrimper.constructorTree(c, log)(t, resolutionFallback) orElse CompanionCrimper .applyTree(c, log)(t, resolutionFallback)) getOrElse c.abort(c.enclosingPosition, s"Failed for [$t]") - println(s"CONSTRUCTED [$r]") + log(s"Constructed [$r]") r } @@ -71,7 +67,7 @@ object MacwireCatsEffectMacros { case (resource, acc) => q"${resource.value}.flatMap((${resource.ident}: ${resource.tpe}) => $acc)" } - log(s"CODE: [$code]") + log(s"Code: [$code]") c.Expr[CatsResource[IO, T]](code) } From 46b38357e1a7b34b6977f749ad9fc530aa0b9de0 Mon Sep 17 00:00:00 2001 From: adamw Date: Tue, 12 Oct 2021 10:49:36 +0200 Subject: [PATCH 11/32] Add some tests for wireApp --- .../test-cases/simpleAutoParameters.success | 18 ++++++++++++++++ .../simpleMultiLevelNoParameters.success | 20 ++++++++++++++++++ .../simpleMultiLevelProvidedParameter.success | 21 +++++++++++++++++++ .../test-cases/simpleNoParameters.success | 17 +++++++++++++++ .../simpleProvidedParameter.success | 19 +++++++++++++++++ 5 files changed, 95 insertions(+) create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/simpleAutoParameters.success create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/simpleNoParameters.success create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/simpleProvidedParameter.success diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleAutoParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleAutoParameters.success new file mode 100644 index 00000000..8b13d860 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleAutoParameters.success @@ -0,0 +1,18 @@ +import com.softwaremill.macwire.catseffectsupport._ + +#include commonSimpleClasses +import cats.effect._ + +trait Test { + def theC: Resource[IO, C] = wireApp[C]() +} + +val t = new Test {} + +val theC: C = { + import cats.effect.unsafe.implicits.global + t.theC.allocated.unsafeRunSync()._1 +} + +require(theC.a != null) +require(theC.b != null) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success new file mode 100644 index 00000000..ee7ef141 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success @@ -0,0 +1,20 @@ +import com.softwaremill.macwire.catseffectsupport._ + +#include commonSimpleClasses +import cats.effect._ + +trait Test { + def theE: Resource[IO, E] = wireApp[E]() +} + +val t = new Test {} + +val theE: E = { + import cats.effect.unsafe.implicits.global + t.theE.allocated.unsafeRunSync()._1 +} + +require(theE.a != null) +require(theE.c != null) +require(theE.c.a != null) +require(theE.c.b != null) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success new file mode 100644 index 00000000..057f82d9 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success @@ -0,0 +1,21 @@ +import com.softwaremill.macwire.catseffectsupport._ + +#include commonSimpleClasses +import cats.effect._ + +trait Test { + val theA: A = A() + def theE: Resource[IO, E] = wireApp[E](a) +} + +val t = new Test {} + +val theE: E = { + import cats.effect.unsafe.implicits.global + t.theE.allocated.unsafeRunSync()._1 +} + +require(theE.a eq t.theA) +require(theE.c != null) +require(theE.c.a eq t.theA) +require(theE.c.b != null) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleNoParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleNoParameters.success new file mode 100644 index 00000000..7a4d9d33 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleNoParameters.success @@ -0,0 +1,17 @@ +import com.softwaremill.macwire.catseffectsupport._ + +#include commonSimpleClasses +import cats.effect._ + +trait Test { + def theA: Resource[IO, A] = wireApp[A]() +} + +val t = new Test {} + +val theA: A = { + import cats.effect.unsafe.implicits.global + t.theA.allocated.unsafeRunSync()._1 +} + +require(theA != null) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleProvidedParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleProvidedParameter.success new file mode 100644 index 00000000..2aeeebad --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleProvidedParameter.success @@ -0,0 +1,19 @@ +import com.softwaremill.macwire.catseffectsupport._ + +#include commonSimpleClasses +import cats.effect._ + +trait Test { + val theA = A() + def theC: Resource[IO, C] = wireApp[C](theA) +} + +val t = new Test {} + +val theC: C = { + import cats.effect.unsafe.implicits.global + t.theC.allocated.unsafeRunSync()._1 +} + +require(theC.a eq t.theA) +require(theC.b != null) From 6a9888a9a1cdbe490396d965cb8c20f73d17d8fd Mon Sep 17 00:00:00 2001 From: adamw Date: Tue, 12 Oct 2021 11:56:02 +0200 Subject: [PATCH 12/32] Add assertions to tests, refactor tests --- .../test-cases/resourceMissingDeps.failure | 8 +++++++ ...esourceMultiLevelProvidedParameter.success | 24 +++++++++++++++++++ .../resourceProvidedParameters.success | 23 ++++++++++++++++++ .../test-cases/simpleUsingCompanion.success | 14 +++++++++++ .../resources/test-cases/wireApp-flat.success | 13 ---------- .../test-cases/wireApp-missing-deps.failure | 12 ---------- .../test-cases/wireApp-nested.success | 15 ------------ .../wireApp-nestedWithCompanion.success | 18 -------------- .../wireApp-useEmptyConstructor.success | 12 ---------- .../catseffectsupport/CompileTests.scala | 2 +- 10 files changed, 70 insertions(+), 71 deletions(-) create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/resourceMissingDeps.failure create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/resourceProvidedParameters.success create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/simpleUsingCompanion.success delete mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/wireApp-flat.success delete mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/wireApp-missing-deps.failure delete mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nested.success delete mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nestedWithCompanion.success delete mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/wireApp-useEmptyConstructor.success diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/resourceMissingDeps.failure b/macrosCatsEffectTests/src/test/resources/test-cases/resourceMissingDeps.failure new file mode 100644 index 00000000..ba0b9c7f --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/resourceMissingDeps.failure @@ -0,0 +1,8 @@ +import com.softwaremill.macwire.catseffectsupport._ +import cats.effect._ + +class B(s: String) + +object Test { + val theB: Resource[IO, B] = wireApp[B]() +} \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success new file mode 100644 index 00000000..2639a2a8 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success @@ -0,0 +1,24 @@ +import com.softwaremill.macwire.catseffectsupport._ + +#include commonSimpleClasses +import cats.effect._ + +val allocated = scala.collection.mutable.ListBuffer[String]() + +object Test { + val theA: Resource[IO, A] = Resource.make(IO { allocated.addOne("A"); A() })(_ => IO.unit) + val theE: Resource[IO, E] = wireApp[E](theA) +} + +val theE: E = { + import cats.effect.unsafe.implicits.global + Test.theE.allocated.unsafeRunSync()._1 +} + +require(theE.a != null) +require(theE.c != null) +require(theE.c.a != null) +require(theE.c.b != null) + +require(allocated.contains("A")) +require(allocated.length == 1) // should be allocated once diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/resourceProvidedParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/resourceProvidedParameters.success new file mode 100644 index 00000000..35912628 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/resourceProvidedParameters.success @@ -0,0 +1,23 @@ +import com.softwaremill.macwire.catseffectsupport._ + +#include commonSimpleClasses +import cats.effect._ + +val allocated = scala.collection.mutable.Set[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 theC: Resource[IO, C] = wireApp[C](theA, theB) +} + +val theC: C = { + import cats.effect.unsafe.implicits.global + Test.theC.allocated.unsafeRunSync()._1 +} + +require(theC.a != null) +require(theC.b != null) + +require(allocated.contains("A")) +require(allocated.contains("B")) \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleUsingCompanion.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleUsingCompanion.success new file mode 100644 index 00000000..36ead8ed --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleUsingCompanion.success @@ -0,0 +1,14 @@ +import com.softwaremill.macwire.catseffectsupport._ +import cats.effect._ + +object Test { + class A() + class B private (a: A) + object B { + def apply(a: A): B = new B(a) + } + + val theB: Resource[IO, B] = wireApp[B]() +} + +require(Test.theB != null) \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-flat.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-flat.success deleted file mode 100644 index 9e2f41f9..00000000 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-flat.success +++ /dev/null @@ -1,13 +0,0 @@ -import cats.effect._ - -class DatabaseAccess() -class SecurityFilter() -class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) - -object UserModule { - import com.softwaremill.macwire.catseffectsupport._ - - val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) - val theSecurityFilter: Resource[IO, SecurityFilter] = Resource.pure(new SecurityFilter()) - val theUserFinder: Resource[IO, UserFinder] = wireApp[UserFinder](theDatabaseAccess, theSecurityFilter) -} \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-missing-deps.failure b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-missing-deps.failure deleted file mode 100644 index 5ecd5c55..00000000 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-missing-deps.failure +++ /dev/null @@ -1,12 +0,0 @@ -import cats.effect._ - -class DatabaseAccess() -class SecurityFilter(s: String) -class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) - -object UserModule { - import com.softwaremill.macwire.catseffectsupport._ - - val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) - val theUserFinder: Resource[IO, UserFinder] = wireApp[UserFinder](theDatabaseAccess) -} \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nested.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nested.success deleted file mode 100644 index d6635530..00000000 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nested.success +++ /dev/null @@ -1,15 +0,0 @@ -import cats.effect._ - -class DatabaseAccess() -class SecurityFilter(databaseAccess: DatabaseAccess) -class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) -class UserStatusReader(databaseAccess: DatabaseAccess, userFinder: UserFinder) - -object UserModule { - import com.softwaremill.macwire.catseffectsupport._ - - val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) - - val theUserStatusReader: Resource[IO, UserStatusReader] = wireApp[UserStatusReader](theDatabaseAccess) - -} \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nestedWithCompanion.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nestedWithCompanion.success deleted file mode 100644 index bdcfc5d1..00000000 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-nestedWithCompanion.success +++ /dev/null @@ -1,18 +0,0 @@ -import cats.effect._ - -object UserModule { - import com.softwaremill.macwire.catseffectsupport._ - - class DatabaseAccess() - class SecurityFilter private (databaseAccess: DatabaseAccess) - object SecurityFilter { - def apply(databaseAccess: DatabaseAccess): SecurityFilter = new SecurityFilter(databaseAccess) - } - - class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) - class UserStatusReader(databaseAccess: DatabaseAccess, userFinder: UserFinder) - - val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) - - val theUserStatusReader: Resource[IO, UserStatusReader] = wireApp[UserStatusReader](theDatabaseAccess) -} \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-useEmptyConstructor.success b/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-useEmptyConstructor.success deleted file mode 100644 index 33a84527..00000000 --- a/macrosCatsEffectTests/src/test/resources/test-cases/wireApp-useEmptyConstructor.success +++ /dev/null @@ -1,12 +0,0 @@ -import cats.effect._ - -class DatabaseAccess() -class SecurityFilter() -class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) - -object UserModule { - import com.softwaremill.macwire.catseffectsupport._ - - val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) - val theUserFinder: Resource[IO, UserFinder] = wireApp[UserFinder](theDatabaseAccess) -} \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala index 61e0df37..ab560e61 100644 --- a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala +++ b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala @@ -6,7 +6,7 @@ class CompileTests extends CompileTestsSupport { runTestsWith( expectedFailures = List( - "wireApp-missing-deps" -> List("Cannot find a value of type: [String]") + "resourceMissingDeps" -> List("Cannot find a value of type: [String]") ), expectedWarnings = List() ) From 90b60e4bb5e00f54e0bb50400ab518c97de4c709 Mon Sep 17 00:00:00 2001 From: adamw Date: Tue, 12 Oct 2021 11:57:55 +0200 Subject: [PATCH 13/32] Add test for IOs --- .../test-cases/ioProvidedParameter.success | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/ioProvidedParameter.success diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/ioProvidedParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/ioProvidedParameter.success new file mode 100644 index 00000000..03587c84 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/ioProvidedParameter.success @@ -0,0 +1,23 @@ +import com.softwaremill.macwire.catseffectsupport._ + +#include commonSimpleClasses +import cats.effect._ + +val allocated = scala.collection.mutable.Set[String]() + +object Test { + val theA: IO[A] = IO { allocated.add("A"); A() } + val theB: IO[B] = IO { allocated.add("B"); B() } + val theC: Resource[IO, C] = wireApp[C](theA, theB) +} + +val theC: C = { + import cats.effect.unsafe.implicits.global + Test.theC.allocated.unsafeRunSync()._1 +} + +require(theC.a != null) +require(theC.b != null) + +require(allocated.contains("A")) +require(allocated.contains("B")) \ No newline at end of file From 7e34d33870c3cb7c009316b5d87b2513f45df8d9 Mon Sep 17 00:00:00 2001 From: adamw Date: Tue, 12 Oct 2021 12:25:11 +0200 Subject: [PATCH 14/32] Add factory testsx --- .../test-cases/factoryNoParameters.success | 21 +++++++++++++++++ .../test-cases/factoryWithParameter.success | 21 +++++++++++++++++ .../factoryWithParameterFromResource.success | 23 +++++++++++++++++++ .../simpleMultiLevelNoParameters.success | 2 ++ 4 files changed, 67 insertions(+) create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/factoryNoParameters.success create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameter.success create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameterFromResource.success diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/factoryNoParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/factoryNoParameters.success new file mode 100644 index 00000000..88b224e8 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/factoryNoParameters.success @@ -0,0 +1,21 @@ +import com.softwaremill.macwire.catseffectsupport._ + +#include commonSimpleClasses +import cats.effect._ + +val created = scala.collection.mutable.Set[String]() + +object Test { + def theA: A = { created.add("A"); A() } + val theC: Resource[IO, C] = wireApp[C](theA) +} + +val theC: C = { + import cats.effect.unsafe.implicits.global + Test.theC.allocated.unsafeRunSync()._1 +} + +require(theC.a != null) +require(theC.b != null) + +require(created.contains("A")) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameter.success new file mode 100644 index 00000000..a9ed2695 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameter.success @@ -0,0 +1,21 @@ +import com.softwaremill.macwire.catseffectsupport._ + +#include commonSimpleClasses +import cats.effect._ + +val created = scala.collection.mutable.Set[String]() + +object Test { + def theA(b: B): A = { created.add(s"A using $b"); A() } + val theC: Resource[IO, C] = wireApp[C](theA _) +} + +val theC: C = { + import cats.effect.unsafe.implicits.global + Test.theC.allocated.unsafeRunSync()._1 +} + +require(theC.a != null) +require(theC.b != null) + +require(created.contains("A using B()")) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameterFromResource.success b/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameterFromResource.success new file mode 100644 index 00000000..3a9b5424 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameterFromResource.success @@ -0,0 +1,23 @@ +import com.softwaremill.macwire.catseffectsupport._ + +#include commonSimpleClasses +import cats.effect._ + +val factoryParams = scala.collection.mutable.ListBuffer[B]() + +object Test { + def theA(b: B): A = { factoryParams.addOne(b); A() } + val theB: Resource[IO, B] = Resource.pure(B()) + val theC: Resource[IO, C] = wireApp[C](theA _, theB) +} + +val theC: C = { + import cats.effect.unsafe.implicits.global + Test.theC.allocated.unsafeRunSync()._1 +} + +require(theC.a != null) +require(theC.b != null) + +require(factoryParams.length == 1) +require(factoryParams.head eq theC.b) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success index ee7ef141..2fe54119 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success @@ -18,3 +18,5 @@ require(theE.a != null) require(theE.c != null) require(theE.c.a != null) require(theE.c.b != null) + +require(theE.a neq theE.c.a) \ No newline at end of file From 2b59d7517fb8901007d06ecde2f85d5013efbaee Mon Sep 17 00:00:00 2001 From: adamw Date: Tue, 12 Oct 2021 12:27:36 +0200 Subject: [PATCH 15/32] Failure tests with path --- ...ceMissingDeps.failure => simpleMissingDeps.failure} | 0 .../test-cases/simpleMissingDepsMultiLevel.failure | 10 ++++++++++ .../macwire/catseffectsupport/CompileTests.scala | 3 ++- 3 files changed, 12 insertions(+), 1 deletion(-) rename macrosCatsEffectTests/src/test/resources/test-cases/{resourceMissingDeps.failure => simpleMissingDeps.failure} (100%) create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/resourceMissingDeps.failure b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDeps.failure similarity index 100% rename from macrosCatsEffectTests/src/test/resources/test-cases/resourceMissingDeps.failure rename to macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDeps.failure diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure new file mode 100644 index 00000000..3fa5d2e4 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure @@ -0,0 +1,10 @@ +import com.softwaremill.macwire.catseffectsupport._ +import cats.effect._ + +class A(s: String) +class B(a: A) +class C(b: B) + +object Test { + val theC: Resource[IO, C] = wireApp[C]() +} \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala index ab560e61..46ae7ec0 100644 --- a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala +++ b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala @@ -6,7 +6,8 @@ class CompileTests extends CompileTestsSupport { runTestsWith( expectedFailures = List( - "resourceMissingDeps" -> List("Cannot find a value of type: [String]") + "simpleMissingDeps" -> List("Cannot find a value of type: [String], path: B.s"), + "simpleMissingDepsMultiLevel" -> List("Cannot find a value of type: [String], path: C.b.a.s") ), expectedWarnings = List() ) From 18cbd081c8c858a2709c9edbf1c96cd8e1c1dcda Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Tue, 12 Oct 2021 18:11:31 +0200 Subject: [PATCH 16/32] Move IO wrapped instances out of the scope for the current work --- .../test-cases/simpleMultiLevelProvidedParameter.success | 2 +- .../test-cases/{ => todo}/ioProvidedParameter.success | 0 .../softwaremill/macwire/catseffectsupport/CompileTests.scala | 4 ++-- 3 files changed, 3 insertions(+), 3 deletions(-) rename macrosCatsEffectTests/src/test/resources/test-cases/{ => todo}/ioProvidedParameter.success (100%) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success index 057f82d9..9be9b623 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success @@ -5,7 +5,7 @@ import cats.effect._ trait Test { val theA: A = A() - def theE: Resource[IO, E] = wireApp[E](a) + def theE: Resource[IO, E] = wireApp[E](theA) } val t = new Test {} diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/ioProvidedParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/todo/ioProvidedParameter.success similarity index 100% rename from macrosCatsEffectTests/src/test/resources/test-cases/ioProvidedParameter.success rename to macrosCatsEffectTests/src/test/resources/test-cases/todo/ioProvidedParameter.success diff --git a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala index 46ae7ec0..2245c694 100644 --- a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala +++ b/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala @@ -6,8 +6,8 @@ class CompileTests extends CompileTestsSupport { runTestsWith( expectedFailures = List( - "simpleMissingDeps" -> List("Cannot find a value of type: [String], path: B.s"), - "simpleMissingDepsMultiLevel" -> List("Cannot find a value of type: [String], path: C.b.a.s") + "simpleMissingDeps" -> List("Cannot find a value of type: [String]"), //TODO List("Cannot find a value of type: [String], path: B.s") + "simpleMissingDepsMultiLevel" -> List("Cannot find a value of type: [String]") //TODO List("Cannot find a value of type: [String], path: C.b.a.s") ), expectedWarnings = List() ) From 1373434613418516ed0fb5cdf8577d15007709ff Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Tue, 12 Oct 2021 19:17:01 +0200 Subject: [PATCH 17/32] Add support for instances --- .../MacwireCatsEffectMacros.scala | 57 +++++++++++++------ 1 file changed, 39 insertions(+), 18 deletions(-) diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala index 5cf1ca4e..6bd30229 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -16,26 +16,45 @@ object MacwireCatsEffectMacros { val targetType = implicitly[c.WeakTypeTag[T]] lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) - case class Resource(value: Tree) { - lazy val resourceType = typeCheckUtil.typeCheckIfNeeded(value).typeArgs(1) - lazy val ident = Ident(TermName(c.freshName())) + trait Provider { + def ident: Tree + 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) } - val resources = dependencies - .map { expr => - val checkedType = typeCheckUtil.typeCheckIfNeeded(expr.tree) + case class Instance(value: Tree) extends Provider { + lazy val `type`: Type = typeCheckUtil.typeCheckIfNeeded(value) + lazy val ident: Tree = value + } - if (!checkedType.typeSymbol.fullName.startsWith("cats.effect.kernel.Resource")) - c.abort(c.enclosingPosition, s"Unsupported resource type [$checkedType].") - else if (checkedType.typeArgs.size != 2) - c.abort(c.enclosingPosition, s"Expected 2 type args, but found [${checkedType.typeArgs.size}].") - else Resource(expr.tree) - } - .map(r => (r.resourceType, r)) + def isResource(expr: Expr[Any]): Boolean = { + val checkedType = typeCheckUtil.typeCheckIfNeeded(expr.tree) + + checkedType.typeSymbol.fullName.startsWith("cats.effect.kernel.Resource") && checkedType.typeArgs.size == 2 + } + + val resourcesExprs = dependencies.filter(isResource) + val instancesExprs = dependencies.filterNot(isResource) + + val resources = resourcesExprs + .map(expr => Resource(expr.tree)) + .map(r => (r.`type`, r)) .toMap - log(s"RESOURCES: [${resources.mkString(", ")}]") + val instances = instancesExprs + .map(expr => Instance(expr.tree)) + .map(i => (i.`type`, i)) + .toMap + + log(s"resources: [${resources.mkString(", ")}]") + log(s"instances: [${instances.mkString(", ")}]") + + def findInstance(t: Type): Option[Instance] = instances.get(t) def findResource(t: Type): Option[Resource] = resources.get(t) @@ -45,15 +64,17 @@ object MacwireCatsEffectMacros { !name.startsWith("java.lang.") && !name.startsWith("scala.") } - lazy val resolutionFallback: (c.Symbol, c.Type) => c.Tree = (_, tpe) => - if (isWireable(tpe)) findResource(tpe).map(_.ident).getOrElse(go(tpe)) + def findeProvider(tpe: Type): Option[Provider] = findInstance(tpe).orElse(findResource(tpe)) + + lazy val resolutionWithFallback: (Symbol, Type) => Tree = (_, tpe) => + if (isWireable(tpe)) findeProvider(tpe).map(_.ident).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, resolutionFallback) orElse CompanionCrimper - .applyTree(c, log)(t, resolutionFallback)) getOrElse + (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]") From 0d2d0d039df854909ddf800a10ff845b5e7d0d5f Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Tue, 12 Oct 2021 21:31:15 +0200 Subject: [PATCH 18/32] Add basic support for factory methods --- .../MacwireCatsEffectMacros.scala | 55 +++++++++++++++++-- .../simpleMultiLevelNoParameters.success | 9 ++- 2 files changed, 58 insertions(+), 6 deletions(-) diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala index 6bd30229..98ff9bd3 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -13,11 +13,12 @@ object MacwireCatsEffectMacros { )(dependencies: c.Expr[Any]*): c.Expr[CatsResource[IO, T]] = { import c.universe._ + type Resolver = (Symbol, Type) => Tree + val targetType = implicitly[c.WeakTypeTag[T]] lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) trait Provider { - def ident: Tree def `type`: Type } @@ -32,14 +33,52 @@ object MacwireCatsEffectMacros { lazy val ident: Tree = value } + case class FactoryMethod(value: Tree) extends Provider { + val (params, fun) = value 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) + // Function with two parameter lists (implicit parameters) (>=2.13) + case Function(p, Apply(Apply(f, _), _)) => (p, f) + case Function(p, Apply(f, _)) => (p, f) + // Other types not supported + case _ => c.abort(c.enclosingPosition, s"Not supported factory type: [$value]") + } + + 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)) + } + + q"$fun(..$values)" + } + + + } + def isResource(expr: Expr[Any]): Boolean = { val checkedType = typeCheckUtil.typeCheckIfNeeded(expr.tree) checkedType.typeSymbol.fullName.startsWith("cats.effect.kernel.Resource") && checkedType.typeArgs.size == 2 } + 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 + } + + val resourcesExprs = dependencies.filter(isResource) - val instancesExprs = dependencies.filterNot(isResource) + val factoryMethodsExprs = dependencies.filter(isFactoryMethod) + val instancesExprs = dependencies.diff(resourcesExprs).diff(factoryMethodsExprs) val resources = resourcesExprs .map(expr => Resource(expr.tree)) @@ -51,12 +90,20 @@ object MacwireCatsEffectMacros { .map(i => (i.`type`, i)) .toMap + 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 findInstance(t: Type): Option[Instance] = instances.get(t) def findResource(t: Type): Option[Resource] = resources.get(t) + def findFactoryMethod(t: Type): Option[FactoryMethod] = factoryMethods.get(t) def isWireable(tpe: Type): Boolean = { val name = tpe.typeSymbol.fullName @@ -64,10 +111,10 @@ object MacwireCatsEffectMacros { !name.startsWith("java.lang.") && !name.startsWith("scala.") } - def findeProvider(tpe: Type): Option[Provider] = findInstance(tpe).orElse(findResource(tpe)) + def findeProvider(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)) findeProvider(tpe).map(_.ident).getOrElse(go(tpe)) + if (isWireable(tpe)) findeProvider(tpe).getOrElse(go(tpe)) else c.abort(c.enclosingPosition, s"Cannot find a value of type: [${tpe}]") def go(t: Type): Tree = { diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success index 2fe54119..b0880634 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success @@ -1,8 +1,13 @@ import com.softwaremill.macwire.catseffectsupport._ -#include commonSimpleClasses import cats.effect._ +class A() +class B() +class C(val a: A, val b: B) + +class E(val a: A, val c: C) + trait Test { def theE: Resource[IO, E] = wireApp[E]() } @@ -19,4 +24,4 @@ require(theE.c != null) require(theE.c.a != null) require(theE.c.b != null) -require(theE.a neq theE.c.a) \ No newline at end of file +require(theE.a != theE.c.a) \ No newline at end of file From 42b5eea51bcd9f9710541646ab549df41b1606f2 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Tue, 12 Oct 2021 21:32:09 +0200 Subject: [PATCH 19/32] Add todo for resource factory method --- .../resourceFactoryWithParameters.success | 23 +++++++++++++++++++ ...esourceMultiLevelProvidedParameter.success | 2 +- .../factoryWithParameterFromResource.success | 2 +- 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 macrosCatsEffectTests/src/test/resources/test-cases/resourceFactoryWithParameters.success rename macrosCatsEffectTests/src/test/resources/test-cases/{ => todo}/factoryWithParameterFromResource.success (85%) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/resourceFactoryWithParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/resourceFactoryWithParameters.success new file mode 100644 index 00000000..db73e5a2 --- /dev/null +++ b/macrosCatsEffectTests/src/test/resources/test-cases/resourceFactoryWithParameters.success @@ -0,0 +1,23 @@ +import com.softwaremill.macwire.catseffectsupport._ + +#include commonSimpleClasses +import cats.effect._ + +val factoryParams = scala.collection.mutable.ListBuffer[B]() + +object Test { + def theA(b: B): A = { factoryParams.append(b); A() } + val theB: Resource[IO, B] = Resource.pure(B()) + val theC: Resource[IO, C] = wireApp[C](theA _, theB) +} + +val theC: C = { + import cats.effect.unsafe.implicits.global + Test.theC.allocated.unsafeRunSync()._1 +} + +require(theC.a != null) +require(theC.b != null) + +require(factoryParams.length == 1) +require(factoryParams.head eq theC.b) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success index 2639a2a8..71966052 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success @@ -6,7 +6,7 @@ import cats.effect._ val allocated = scala.collection.mutable.ListBuffer[String]() object Test { - val theA: Resource[IO, A] = Resource.make(IO { allocated.addOne("A"); A() })(_ => IO.unit) + val theA: Resource[IO, A] = Resource.make(IO { allocated.append("A"); A() })(_ => IO.unit) val theE: Resource[IO, E] = wireApp[E](theA) } diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameterFromResource.success b/macrosCatsEffectTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success similarity index 85% rename from macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameterFromResource.success rename to macrosCatsEffectTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success index 3a9b5424..d6dac193 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameterFromResource.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success @@ -6,7 +6,7 @@ import cats.effect._ val factoryParams = scala.collection.mutable.ListBuffer[B]() object Test { - def theA(b: B): A = { factoryParams.addOne(b); A() } + def theA(b: B): Resource[IO, A] = { factoryParams.append(b); Resource.pure(A()) } val theB: Resource[IO, B] = Resource.pure(B()) val theC: Resource[IO, C] = wireApp[C](theA _, theB) } From 0d797bedd131568ced50c0d8620e716881cd7b09 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Thu, 14 Oct 2021 08:09:18 +0200 Subject: [PATCH 20/32] Rename --- .../macwire/catseffectsupport/MacwireCatsEffectMacros.scala | 2 +- .../com/softwaremill/macwire/catseffectsupport/package.scala | 2 +- .../src/test/resources/test-cases/factoryNoParameters.success | 2 +- .../src/test/resources/test-cases/factoryWithParameter.success | 2 +- .../resources/test-cases/resourceFactoryWithParameters.success | 2 +- .../test-cases/resourceMultiLevelProvidedParameter.success | 2 +- .../resources/test-cases/resourceProvidedParameters.success | 2 +- .../src/test/resources/test-cases/simpleAutoParameters.success | 2 +- .../src/test/resources/test-cases/simpleMissingDeps.failure | 2 +- .../resources/test-cases/simpleMissingDepsMultiLevel.failure | 2 +- .../resources/test-cases/simpleMultiLevelNoParameters.success | 2 +- .../test-cases/simpleMultiLevelProvidedParameter.success | 2 +- .../src/test/resources/test-cases/simpleNoParameters.success | 2 +- .../test/resources/test-cases/simpleProvidedParameter.success | 2 +- .../src/test/resources/test-cases/simpleUsingCompanion.success | 2 +- .../test-cases/todo/factoryWithParameterFromResource.success | 2 +- .../test/resources/test-cases/todo/ioProvidedParameter.success | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala index 98ff9bd3..9dfa2558 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -8,7 +8,7 @@ import com.softwaremill.macwire.internals._ object MacwireCatsEffectMacros { private val log = new Logger() - def wireApp_impl[T: c.WeakTypeTag]( + def autowire_impl[T: c.WeakTypeTag]( c: blackbox.Context )(dependencies: c.Expr[Any]*): c.Expr[CatsResource[IO, T]] = { import c.universe._ diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala index 3e5fc181..95c7b16b 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala @@ -3,5 +3,5 @@ package com.softwaremill.macwire import cats.effect._ package object catseffectsupport { - def wireApp[T](dependencies: Any*): Resource[IO, T] = macro MacwireCatsEffectMacros.wireApp_impl[T] + def autowire[T](dependencies: Any*): Resource[IO, T] = macro MacwireCatsEffectMacros.autowire_impl[T] } \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/factoryNoParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/factoryNoParameters.success index 88b224e8..8be101ad 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/factoryNoParameters.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/factoryNoParameters.success @@ -7,7 +7,7 @@ val created = scala.collection.mutable.Set[String]() object Test { def theA: A = { created.add("A"); A() } - val theC: Resource[IO, C] = wireApp[C](theA) + val theC: Resource[IO, C] = autowire[C](theA) } val theC: C = { diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameter.success index a9ed2695..2168f521 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameter.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameter.success @@ -7,7 +7,7 @@ val created = scala.collection.mutable.Set[String]() object Test { def theA(b: B): A = { created.add(s"A using $b"); A() } - val theC: Resource[IO, C] = wireApp[C](theA _) + val theC: Resource[IO, C] = autowire[C](theA _) } val theC: C = { diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/resourceFactoryWithParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/resourceFactoryWithParameters.success index db73e5a2..be18c52d 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/resourceFactoryWithParameters.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/resourceFactoryWithParameters.success @@ -8,7 +8,7 @@ val factoryParams = scala.collection.mutable.ListBuffer[B]() object Test { def theA(b: B): A = { factoryParams.append(b); A() } val theB: Resource[IO, B] = Resource.pure(B()) - val theC: Resource[IO, C] = wireApp[C](theA _, theB) + val theC: Resource[IO, C] = autowire[C](theA _, theB) } val theC: C = { diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success index 71966052..51a50b47 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success @@ -7,7 +7,7 @@ val allocated = scala.collection.mutable.ListBuffer[String]() object Test { val theA: Resource[IO, A] = Resource.make(IO { allocated.append("A"); A() })(_ => IO.unit) - val theE: Resource[IO, E] = wireApp[E](theA) + val theE: Resource[IO, E] = autowire[E](theA) } val theE: E = { diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/resourceProvidedParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/resourceProvidedParameters.success index 35912628..1ef29318 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/resourceProvidedParameters.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/resourceProvidedParameters.success @@ -8,7 +8,7 @@ val allocated = scala.collection.mutable.Set[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 theC: Resource[IO, C] = wireApp[C](theA, theB) + val theC: Resource[IO, C] = autowire[C](theA, theB) } val theC: C = { diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleAutoParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleAutoParameters.success index 8b13d860..616fb080 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleAutoParameters.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleAutoParameters.success @@ -4,7 +4,7 @@ import com.softwaremill.macwire.catseffectsupport._ import cats.effect._ trait Test { - def theC: Resource[IO, C] = wireApp[C]() + def theC: Resource[IO, C] = autowire[C]() } val t = new Test {} diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDeps.failure b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDeps.failure index ba0b9c7f..88d1c803 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDeps.failure +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDeps.failure @@ -4,5 +4,5 @@ import cats.effect._ class B(s: String) object Test { - val theB: Resource[IO, B] = wireApp[B]() + val theB: Resource[IO, B] = autowire[B]() } \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure index 3fa5d2e4..61490f4d 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure @@ -6,5 +6,5 @@ class B(a: A) class C(b: B) object Test { - val theC: Resource[IO, C] = wireApp[C]() + val theC: Resource[IO, C] = autowire[C]() } \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success index b0880634..014ec97f 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success @@ -9,7 +9,7 @@ class C(val a: A, val b: B) class E(val a: A, val c: C) trait Test { - def theE: Resource[IO, E] = wireApp[E]() + def theE: Resource[IO, E] = autowire[E]() } val t = new Test {} diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success index 9be9b623..31a0be29 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success @@ -5,7 +5,7 @@ import cats.effect._ trait Test { val theA: A = A() - def theE: Resource[IO, E] = wireApp[E](theA) + def theE: Resource[IO, E] = autowire[E](theA) } val t = new Test {} diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleNoParameters.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleNoParameters.success index 7a4d9d33..5f3dcf45 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleNoParameters.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleNoParameters.success @@ -4,7 +4,7 @@ import com.softwaremill.macwire.catseffectsupport._ import cats.effect._ trait Test { - def theA: Resource[IO, A] = wireApp[A]() + def theA: Resource[IO, A] = autowire[A]() } val t = new Test {} diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleProvidedParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleProvidedParameter.success index 2aeeebad..8141e6b2 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleProvidedParameter.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleProvidedParameter.success @@ -5,7 +5,7 @@ import cats.effect._ trait Test { val theA = A() - def theC: Resource[IO, C] = wireApp[C](theA) + def theC: Resource[IO, C] = autowire[C](theA) } val t = new Test {} diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleUsingCompanion.success b/macrosCatsEffectTests/src/test/resources/test-cases/simpleUsingCompanion.success index 36ead8ed..f59bfada 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleUsingCompanion.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/simpleUsingCompanion.success @@ -8,7 +8,7 @@ object Test { def apply(a: A): B = new B(a) } - val theB: Resource[IO, B] = wireApp[B]() + val theB: Resource[IO, B] = autowire[B]() } require(Test.theB != null) \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success b/macrosCatsEffectTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success index d6dac193..3c82ef95 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success @@ -8,7 +8,7 @@ val factoryParams = scala.collection.mutable.ListBuffer[B]() object Test { def theA(b: B): Resource[IO, A] = { factoryParams.append(b); Resource.pure(A()) } val theB: Resource[IO, B] = Resource.pure(B()) - val theC: Resource[IO, C] = wireApp[C](theA _, theB) + val theC: Resource[IO, C] = autowire[C](theA _, theB) } val theC: C = { diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/todo/ioProvidedParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/todo/ioProvidedParameter.success index 03587c84..57fbea1e 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/todo/ioProvidedParameter.success +++ b/macrosCatsEffectTests/src/test/resources/test-cases/todo/ioProvidedParameter.success @@ -8,7 +8,7 @@ val allocated = scala.collection.mutable.Set[String]() object Test { val theA: IO[A] = IO { allocated.add("A"); A() } val theB: IO[B] = IO { allocated.add("B"); B() } - val theC: Resource[IO, C] = wireApp[C](theA, theB) + val theC: Resource[IO, C] = autowire[C](theA, theB) } val theC: C = { From 066f0a9a73f5de241e43d8ea57f030ecfe99eda3 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Thu, 14 Oct 2021 08:41:47 +0200 Subject: [PATCH 21/32] Update readme --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/README.md b/README.md index f4554ad6..7a4a5df8 100644 --- a/README.md +++ b/README.md @@ -209,6 +209,52 @@ trait UserModule { This feature is inspired by @yakivy's work on [jam](https://github.com/yakivy/jam). +## Auto wiring + +In case you need to build an instance from some particular instances and factory methods it's recommended to use `autowire`. This feature is intended to interpolate with fp libraries (currently we support `cats`). + +`autowire` takes as an argument a list which may contain: +* instances (e.g. `new A()`) +* factory methods (e.g. `C.create _`) +* cats.effect.Resource (e.g. `cats.effect.Resource[IO](new A())`) +Based on the given list it creates a set of available instances and performs `wireRec` bypassing the instances search phase. The result of the wiring is always wrapped in `cats.effect.Resource`. For example: + +```Scala +import cats.effect._ + +class DatabaseAccess() + +class SecurityFilter private (databaseAccess: DatabaseAccess) +object SecurityFilter { + def apply(databaseAccess: DatabaseAccess): SecurityFilter = new SecurityFilter(databaseAccess) +} + +class UserFinder(databaseAccess: DatabaseAccess, securityFilter: SecurityFilter) +class UserStatusReader(databaseAccess: DatabaseAccess, userFinder: UserFinder) + +object UserModule { + import com.softwaremill.macwire._ + + val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) + + val theUserStatusReader: Resource[IO, UserStatusReader] = autowire[UserStatusReader](theDatabaseAccess) +} +``` + +will generate +```Scala +[...] +object UserModule { + import com.softwaremill.macwire._ + + val theDatabaseAccess: Resource[IO, DatabaseAccess] = Resource.pure(new DatabaseAccess()) + + val theUserStatusReader: Resource[IO, UserStatusReader] = UserModule.this.theDatabaseAccess.flatMap( + da => Resource.pure[IO, UserStatusReader](new UserStatusReader(da, new UserFinder(da, SecurityFilter.apply(da)))) + ) +} +``` + ## Composing modules Modules (traits or classes containing parts of the object graph) can be combined using inheritance or composition. From f190acd1a4db35e88cd767a46dde73710a9af140 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Thu, 14 Oct 2021 20:08:44 +0200 Subject: [PATCH 22/32] Add IO support --- .../MacwireCatsEffectMacros.scala | 69 +++++++++++-------- .../{todo => }/ioProvidedParameter.success | 0 2 files changed, 42 insertions(+), 27 deletions(-) rename macrosCatsEffectTests/src/test/resources/test-cases/{todo => }/ioProvidedParameter.success (100%) diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala index 9dfa2558..18b1f032 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -37,10 +37,10 @@ object MacwireCatsEffectMacros { val (params, fun) = value 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(f, _))) => (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(f, _)) => (p, f) // Other types not supported case _ => c.abort(c.enclosingPosition, s"Not supported factory type: [$value]") } @@ -48,14 +48,20 @@ object MacwireCatsEffectMacros { 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)) + val values = params.map { case vd @ ValDef(_, name, tpt, rhs) => + resolver(vd.symbol, typeCheckUtil.typeCheckIfNeeded(tpt)) } q"$fun(..$values)" } - + } + + case class Effect(value: Tree) extends Provider { + + override val `type`: Type = typeCheckUtil.typeCheckIfNeeded(value).typeArgs(0) + + lazy val asResource = Resource(q"cats.effect.kernel.Resource.eval[cats.effect.IO, ${`type`}]($value)") } def isResource(expr: Expr[Any]): Boolean = { @@ -63,37 +69,43 @@ object MacwireCatsEffectMacros { checkedType.typeSymbol.fullName.startsWith("cats.effect.kernel.Resource") && checkedType.typeArgs.size == 2 } - + 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 - } + // 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 + } + + def isEffect(expr: Expr[Any]): Boolean = { + val checkedType = typeCheckUtil.typeCheckIfNeeded(expr.tree) + checkedType.typeSymbol.fullName.startsWith("cats.effect.IO") && checkedType.typeArgs.size == 1 + } val resourcesExprs = dependencies.filter(isResource) val factoryMethodsExprs = dependencies.filter(isFactoryMethod) - val instancesExprs = dependencies.diff(resourcesExprs).diff(factoryMethodsExprs) + val effectsExprs = dependencies.filter(isEffect) + val instancesExprs = dependencies.diff(resourcesExprs).diff(factoryMethodsExprs).diff(effectsExprs) - val resources = resourcesExprs - .map(expr => Resource(expr.tree)) - .map(r => (r.`type`, r)) - .toMap + val resources = + (effectsExprs.map(expr => Effect(expr.tree).asResource) ++ resourcesExprs.map(expr => Resource(expr.tree))) + .map(r => (r.`type`, r)) + .toMap val instances = instancesExprs - .map(expr => Instance(expr.tree)) - .map(i => (i.`type`, i)) - .toMap + .map(expr => Instance(expr.tree)) + .map(i => (i.`type`, i)) + .toMap val factoryMethods = factoryMethodsExprs - .map(expr => FactoryMethod(expr.tree)) - .map(i => (i.`type`, i)) - .toMap + .map(expr => FactoryMethod(expr.tree)) + .map(i => (i.`type`, i)) + .toMap log(s"exprs: s[${dependencies.mkString(", ")}]") log(s"resources: [${resources.mkString(", ")}]") @@ -111,7 +123,10 @@ object MacwireCatsEffectMacros { !name.startsWith("java.lang.") && !name.startsWith("scala.") } - def findeProvider(tpe: Type): Option[Tree] = findInstance(tpe).map(_.ident).orElse(findResource(tpe).map(_.ident)).orElse(findFactoryMethod(tpe).map(_.applyWith(resolutionWithFallback))) + def findeProvider(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)) findeProvider(tpe).getOrElse(go(tpe)) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/todo/ioProvidedParameter.success b/macrosCatsEffectTests/src/test/resources/test-cases/ioProvidedParameter.success similarity index 100% rename from macrosCatsEffectTests/src/test/resources/test-cases/todo/ioProvidedParameter.success rename to macrosCatsEffectTests/src/test/resources/test-cases/ioProvidedParameter.success From e71c56196da82b1b87ee28b777da19073230884f Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Thu, 14 Oct 2021 20:43:18 +0200 Subject: [PATCH 23/32] Updated readme --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7a4a5df8..5e9d70ee 100644 --- a/README.md +++ b/README.md @@ -216,7 +216,8 @@ In case you need to build an instance from some particular instances and factory `autowire` takes as an argument a list which may contain: * instances (e.g. `new A()`) * factory methods (e.g. `C.create _`) -* cats.effect.Resource (e.g. `cats.effect.Resource[IO](new A())`) +* cats.effect.Resource (e.g. `cats.effect.Resource[IO].pure(new A())`) +* cats.effect.IO (e.g. `cats.effect.IO.pure(new A())`) Based on the given list it creates a set of available instances and performs `wireRec` bypassing the instances search phase. The result of the wiring is always wrapped in `cats.effect.Resource`. For example: ```Scala From d08ddfc580bd10d1c602f65053a2ce8012f20a5e Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Sat, 16 Oct 2021 10:08:04 +0200 Subject: [PATCH 24/32] Fix typo --- .../macwire/catseffectsupport/MacwireCatsEffectMacros.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala index 18b1f032..697143b3 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala @@ -123,13 +123,13 @@ object MacwireCatsEffectMacros { !name.startsWith("java.lang.") && !name.startsWith("scala.") } - def findeProvider(tpe: Type): Option[Tree] = findInstance(tpe) + 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)) findeProvider(tpe).getOrElse(go(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 = { From cd9266ae0323c87c7fc1047ffc1173b84b52cb56 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Sat, 16 Oct 2021 10:50:10 +0200 Subject: [PATCH 25/32] change package name --- build.sbt | 14 +++++------ .../MacwireCatsEffectMacros.scala | 3 +-- .../macwire/auto/catssupport}/package.scala | 4 ++-- .../test-cases/factoryNoParameters.success | 2 +- .../test-cases/factoryWithParameter.success | 2 +- .../test-cases/ioProvidedParameter.success | 2 +- .../resourceFactoryWithParameters.success | 2 +- ...esourceMultiLevelProvidedParameter.success | 2 +- .../resourceProvidedParameters.success | 2 +- .../test-cases/simpleAutoParameters.success | 2 +- .../test-cases/simpleMissingDeps.failure | 2 +- .../simpleMissingDepsMultiLevel.failure | 2 +- .../simpleMultiLevelNoParameters.success | 2 +- .../simpleMultiLevelProvidedParameter.success | 2 +- .../test-cases/simpleNoParameters.success | 2 +- .../simpleProvidedParameter.success | 2 +- .../test-cases/simpleUsingCompanion.success | 2 +- .../todo/factoryWithParameterFromIO.success | 23 +++++++++++++++++++ .../factoryWithParameterFromResource.success | 2 +- .../auto/catssupport}/CompileTests.scala | 2 +- 20 files changed, 49 insertions(+), 27 deletions(-) rename {macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport => macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport}/MacwireCatsEffectMacros.scala (98%) rename {macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport => macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport}/package.scala (65%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/factoryNoParameters.success (88%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/factoryWithParameter.success (89%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/ioProvidedParameter.success (90%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/resourceFactoryWithParameters.success (90%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success (91%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/resourceProvidedParameters.success (91%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/simpleAutoParameters.success (84%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/simpleMissingDeps.failure (66%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure (71%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/simpleMultiLevelNoParameters.success (88%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success (87%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/simpleNoParameters.success (83%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/simpleProvidedParameter.success (85%) rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/simpleUsingCompanion.success (79%) create mode 100644 macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromIO.success rename {macrosCatsEffectTests => macrosAutoCatsTests}/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success (91%) rename {macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport => macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/auto/catssupport}/CompileTests.scala (90%) diff --git a/build.sbt b/build.sbt index 6521846e..2be472c5 100644 --- a/build.sbt +++ b/build.sbt @@ -82,8 +82,8 @@ lazy val root = project utilTests, macrosAkka, macrosAkkaTests, - macrosCatsEffect, - macrosCatsEffectTests + macrosAutoCats, + macrosAutoCatsTests ).flatMap(_.projectRefs): _* ) @@ -171,19 +171,19 @@ lazy val macrosAkkaTests = projectMatrix .dependsOn(macrosAkka, testUtil) .jvmPlatform(scalaVersions = scala2) - lazy val macrosCatsEffect = projectMatrix - .in(file("macrosCatsEffect")) + lazy val macrosAutoCats = projectMatrix + .in(file("macrosAutoCats")) .settings(commonSettings) .settings(libraryDependencies ++= Seq(catsEffect, cats)) .dependsOn(macros) .jvmPlatform(scalaVersions = scala2) .jsPlatform(scalaVersions = scala2) - lazy val macrosCatsEffectTests = projectMatrix - .in(file("macrosCatsEffectTests")) + lazy val macrosAutoCatsTests = projectMatrix + .in(file("macrosAutoCatsTests")) .settings(testSettings) .settings(libraryDependencies ++= Seq(scalatest, catsEffect)) - .dependsOn(macrosCatsEffect, testUtil) + .dependsOn(macrosAutoCats, testUtil) .jvmPlatform(scalaVersions = scala2) Compile / compile := { diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireCatsEffectMacros.scala similarity index 98% rename from macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala rename to macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireCatsEffectMacros.scala index 697143b3..170c7cb6 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/MacwireCatsEffectMacros.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireCatsEffectMacros.scala @@ -1,5 +1,4 @@ -package com.softwaremill.macwire -package catseffectsupport +package com.softwaremill.macwire.auto.catssupport import scala.reflect.macros.blackbox import cats.effect.{IO, Resource => CatsResource} diff --git a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/package.scala similarity index 65% rename from macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala rename to macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/package.scala index 95c7b16b..31853289 100644 --- a/macrosCatsEffect/src/main/scala/com/softwaremill/macwire/catseffectsupport/package.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/package.scala @@ -1,7 +1,7 @@ -package com.softwaremill.macwire +package com.softwaremill.macwire.auto import cats.effect._ -package object catseffectsupport { +package object catssupport { def autowire[T](dependencies: Any*): Resource[IO, T] = macro MacwireCatsEffectMacros.autowire_impl[T] } \ No newline at end of file diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/factoryNoParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/factoryNoParameters.success similarity index 88% rename from macrosCatsEffectTests/src/test/resources/test-cases/factoryNoParameters.success rename to macrosAutoCatsTests/src/test/resources/test-cases/factoryNoParameters.success index 8be101ad..c1ccbadc 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/factoryNoParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/factoryNoParameters.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameter.success similarity index 89% rename from macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameter.success rename to macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameter.success index 2168f521..4ae375c3 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/factoryWithParameter.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameter.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/ioProvidedParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/ioProvidedParameter.success similarity index 90% rename from macrosCatsEffectTests/src/test/resources/test-cases/ioProvidedParameter.success rename to macrosAutoCatsTests/src/test/resources/test-cases/ioProvidedParameter.success index 57fbea1e..cdbe3e1c 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/ioProvidedParameter.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/ioProvidedParameter.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/resourceFactoryWithParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/resourceFactoryWithParameters.success similarity index 90% rename from macrosCatsEffectTests/src/test/resources/test-cases/resourceFactoryWithParameters.success rename to macrosAutoCatsTests/src/test/resources/test-cases/resourceFactoryWithParameters.success index be18c52d..b816986d 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/resourceFactoryWithParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/resourceFactoryWithParameters.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success similarity index 91% rename from macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success rename to macrosAutoCatsTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success index 51a50b47..b7924c36 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/resourceProvidedParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/resourceProvidedParameters.success similarity index 91% rename from macrosCatsEffectTests/src/test/resources/test-cases/resourceProvidedParameters.success rename to macrosAutoCatsTests/src/test/resources/test-cases/resourceProvidedParameters.success index 1ef29318..2618cccb 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/resourceProvidedParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/resourceProvidedParameters.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleAutoParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/simpleAutoParameters.success similarity index 84% rename from macrosCatsEffectTests/src/test/resources/test-cases/simpleAutoParameters.success rename to macrosAutoCatsTests/src/test/resources/test-cases/simpleAutoParameters.success index 616fb080..c4caacfb 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleAutoParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleAutoParameters.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDeps.failure b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDeps.failure similarity index 66% rename from macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDeps.failure rename to macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDeps.failure index 88d1c803..7cb4d1e3 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDeps.failure +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDeps.failure @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ import cats.effect._ class B(s: String) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure similarity index 71% rename from macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure rename to macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure index 61490f4d..fbfd7143 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ import cats.effect._ class A(s: String) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success similarity index 88% rename from macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success rename to macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success index 014ec97f..d720bce3 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ import cats.effect._ diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success similarity index 87% rename from macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success rename to macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success index 31a0be29..1db66377 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleNoParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/simpleNoParameters.success similarity index 83% rename from macrosCatsEffectTests/src/test/resources/test-cases/simpleNoParameters.success rename to macrosAutoCatsTests/src/test/resources/test-cases/simpleNoParameters.success index 5f3dcf45..47b4cc0f 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleNoParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleNoParameters.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleProvidedParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/simpleProvidedParameter.success similarity index 85% rename from macrosCatsEffectTests/src/test/resources/test-cases/simpleProvidedParameter.success rename to macrosAutoCatsTests/src/test/resources/test-cases/simpleProvidedParameter.success index 8141e6b2..0670912b 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleProvidedParameter.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleProvidedParameter.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/simpleUsingCompanion.success b/macrosAutoCatsTests/src/test/resources/test-cases/simpleUsingCompanion.success similarity index 79% rename from macrosCatsEffectTests/src/test/resources/test-cases/simpleUsingCompanion.success rename to macrosAutoCatsTests/src/test/resources/test-cases/simpleUsingCompanion.success index f59bfada..3de25186 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/simpleUsingCompanion.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleUsingCompanion.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ import cats.effect._ object Test { diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromIO.success b/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromIO.success new file mode 100644 index 00000000..57ad563b --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromIO.success @@ -0,0 +1,23 @@ +import com.softwaremill.macwire.auto.catssupport._ + +#include commonSimpleClasses +import cats.effect._ + +val factoryParams = scala.collection.mutable.ListBuffer[B]() + +object Test { + def theA(b: B): IO[A] = { factoryParams.append(b); IO.pure(A()) } + val theB: Resource[IO, B] = Resource.pure(B()) + val theC: Resource[IO, C] = autowire[C](theA _, theB) +} + +val theC: C = { + import cats.effect.unsafe.implicits.global + Test.theC.allocated.unsafeRunSync()._1 +} + +require(theC.a != null) +require(theC.b != null) + +require(factoryParams.length == 1) +require(factoryParams.head eq theC.b) diff --git a/macrosCatsEffectTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success b/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success similarity index 91% rename from macrosCatsEffectTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success rename to macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success index 3c82ef95..61cf36eb 100644 --- a/macrosCatsEffectTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.catseffectsupport._ +import com.softwaremill.macwire.auto.catssupport._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala b/macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/auto/catssupport/CompileTests.scala similarity index 90% rename from macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala rename to macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/auto/catssupport/CompileTests.scala index 2245c694..5d1fb597 100644 --- a/macrosCatsEffectTests/src/test/scala/com/softwaremill/macwire/catseffectsupport/CompileTests.scala +++ b/macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/auto/catssupport/CompileTests.scala @@ -1,4 +1,4 @@ -package com.softwaremill.macwire.catseffectsupport +package com.softwaremill.macwire.auto.catssupport import com.softwaremill.macwire.CompileTestsSupport From 5a042855eb8a143b8b14d3dfe2617f365a6fdfed Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Sat, 16 Oct 2021 11:06:36 +0200 Subject: [PATCH 26/32] Add subtype test case --- .../test-cases/todo/subtypeParameter.success | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 macrosAutoCatsTests/src/test/resources/test-cases/todo/subtypeParameter.success diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/todo/subtypeParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/todo/subtypeParameter.success new file mode 100644 index 00000000..ef728f86 --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/todo/subtypeParameter.success @@ -0,0 +1,21 @@ +import com.softwaremill.macwire.auto.catssupport._ + +import cats.effect._ + +trait A +class AA(i: Int) extends A + +class B(a: A) + +object Main extends App { + + val theAA = new AA(1) + val theB = { + import cats.effect.unsafe.implicits.global + + val resource = autowire[B](theAA) + resource.allocated.unsafeRunSync()._1 + } + + require(theB.a == theAA) +} From 4e428341721671b6339fc507170a27b63967bb58 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Sat, 16 Oct 2021 11:07:20 +0200 Subject: [PATCH 27/32] Updated readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5e9d70ee..ef18545f 100644 --- a/README.md +++ b/README.md @@ -214,7 +214,7 @@ This feature is inspired by @yakivy's work on [jam](https://github.com/yakivy/ja In case you need to build an instance from some particular instances and factory methods it's recommended to use `autowire`. This feature is intended to interpolate with fp libraries (currently we support `cats`). `autowire` takes as an argument a list which may contain: -* instances (e.g. `new A()`) +* values (e.g. `new A()`) * factory methods (e.g. `C.create _`) * cats.effect.Resource (e.g. `cats.effect.Resource[IO].pure(new A())`) * cats.effect.IO (e.g. `cats.effect.IO.pure(new A())`) From 4232a1932af28de279c83cd336d881b7c8adc15b Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Sat, 16 Oct 2021 11:24:26 +0200 Subject: [PATCH 28/32] Fix name --- ...{MacwireCatsEffectMacros.scala => MacwireAutoCatsMacros.scala} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/{MacwireCatsEffectMacros.scala => MacwireAutoCatsMacros.scala} (100%) diff --git a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireCatsEffectMacros.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireAutoCatsMacros.scala similarity index 100% rename from macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireCatsEffectMacros.scala rename to macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireAutoCatsMacros.scala From 4b70ca2ad80e65e1fda39a99a90ff0f53f63472c Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Sat, 16 Oct 2021 11:31:35 +0200 Subject: [PATCH 29/32] Add support for subtyping --- .../catssupport/MacwireAutoCatsMacros.scala | 8 ++++--- .../test-cases/todo/subtypeParameter.success | 21 ------------------- 2 files changed, 5 insertions(+), 24 deletions(-) delete mode 100644 macrosAutoCatsTests/src/test/resources/test-cases/todo/subtypeParameter.success diff --git a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireAutoCatsMacros.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireAutoCatsMacros.scala index 170c7cb6..63d8701d 100644 --- a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireAutoCatsMacros.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireAutoCatsMacros.scala @@ -111,10 +111,12 @@ object MacwireCatsEffectMacros { log(s"instances: [${instances.mkString(", ")}]") log(s"factory methods: [${factoryMethods.mkString(", ")}]") - def findInstance(t: Type): Option[Instance] = instances.get(t) + def doFind[T <: Provider](values: Map[Type, T])(tpe: Type): Option[T] = values.find {case (t, _) => t <:< tpe}.map(_._2) - def findResource(t: Type): Option[Resource] = resources.get(t) - def findFactoryMethod(t: Type): Option[FactoryMethod] = factoryMethods.get(t) + def findInstance(t: Type): Option[Instance] = doFind(instances)(t) + + def findResource(t: Type): Option[Resource] = doFind(resources)(t) + def findFactoryMethod(t: Type): Option[FactoryMethod] = doFind(factoryMethods)(t) def isWireable(tpe: Type): Boolean = { val name = tpe.typeSymbol.fullName diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/todo/subtypeParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/todo/subtypeParameter.success deleted file mode 100644 index ef728f86..00000000 --- a/macrosAutoCatsTests/src/test/resources/test-cases/todo/subtypeParameter.success +++ /dev/null @@ -1,21 +0,0 @@ -import com.softwaremill.macwire.auto.catssupport._ - -import cats.effect._ - -trait A -class AA(i: Int) extends A - -class B(a: A) - -object Main extends App { - - val theAA = new AA(1) - val theB = { - import cats.effect.unsafe.implicits.global - - val resource = autowire[B](theAA) - resource.allocated.unsafeRunSync()._1 - } - - require(theB.a == theAA) -} From 2bb2a0091596e5f262bbdb9703da40ff09b16948 Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Sat, 16 Oct 2021 12:20:27 +0200 Subject: [PATCH 30/32] Add tagging example --- build.sbt | 2 +- .../test-cases/subtypeParameter.success | 22 ++++++++++ .../test-cases/taggingParameters.success | 42 +++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 macrosAutoCatsTests/src/test/resources/test-cases/subtypeParameter.success create mode 100644 macrosAutoCatsTests/src/test/resources/test-cases/taggingParameters.success diff --git a/build.sbt b/build.sbt index 2be472c5..1e8b7789 100644 --- a/build.sbt +++ b/build.sbt @@ -182,7 +182,7 @@ lazy val macrosAkkaTests = projectMatrix lazy val macrosAutoCatsTests = projectMatrix .in(file("macrosAutoCatsTests")) .settings(testSettings) - .settings(libraryDependencies ++= Seq(scalatest, catsEffect)) + .settings(libraryDependencies ++= Seq(scalatest, catsEffect, tagging)) .dependsOn(macrosAutoCats, testUtil) .jvmPlatform(scalaVersions = scala2) diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/subtypeParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/subtypeParameter.success new file mode 100644 index 00000000..f49799b8 --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/subtypeParameter.success @@ -0,0 +1,22 @@ +import com.softwaremill.macwire.auto.catssupport._ + +import cats.effect._ + +trait A +class AA(i: Int) extends A + +class B(val a: A) + +object Test { + + val theAA: AA = new AA(1) + val theB: B = { + import cats.effect.unsafe.implicits.global + + val resource = autowire[B](theAA) + resource.allocated.unsafeRunSync()._1 + } + +} + +require(Test.theB.a == Test.theAA) diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/taggingParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/taggingParameters.success new file mode 100644 index 00000000..69450571 --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/taggingParameters.success @@ -0,0 +1,42 @@ +import cats.effect._ +import com.softwaremill.tagging._ +import com.softwaremill.macwire.auto.catssupport._ + +class Cache { + val state = scala.collection.mutable.HashMap.empty[String, String] +} + +trait First +trait Second + +class A(val cache: Cache) +class B(val cache: Cache) +class C(val cache: Cache) +class D(val cache: Cache, val a: A, val b: B) +class E(val c: C, val d: D) + +object Test { + + val cache1 = (new Cache()).taggedWith[First] + val cache2 = (new Cache()).taggedWith[Second] + + val theE = { + import cats.effect.unsafe.implicits.global + + val resource = autowire[E]( + cache1, + cache2, + ((c: Cache @@ First) => new A(c)), + ((c: Cache @@ First) => new B(c)), + ((c: Cache @@ Second) => new C(c)), + ((c: Cache @@ First) => new D(c, _, _)) + ) + resource.allocated.unsafeRunSync()._1 + } + +} + +require(Test.theE.d.a.cache == Test.cache1) +require(Test.theE.d.b.cache == Test.cache1) +require(Test.theE.d.cache == Test.cache1) +require(Test.theE.c.cache == Test.cache2) \ No newline at end of file From 53cdea2d8c9a25f301a88f9a961c08096a716bca Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Thu, 21 Oct 2021 18:09:46 +0200 Subject: [PATCH 31/32] change package name --- .../MacwireAutoCatsMacros.scala | 2 +- .../catssupport => autocats}/package.scala | 4 +-- .../test-cases/factoryNoParameters.success | 2 +- .../test-cases/factoryWithParameter.success | 2 +- .../test-cases/ioProvidedParameter.success | 2 +- .../resourceFactoryWithParameters.success | 2 +- ...esourceMultiLevelProvidedParameter.success | 2 +- .../resourceProvidedParameters.success | 2 +- .../test-cases/simpleAutoParameters.success | 2 +- .../test-cases/simpleMissingDeps.failure | 2 +- .../simpleMissingDepsMultiLevel.failure | 2 +- .../simpleMultiLevelNoParameters.success | 2 +- .../simpleMultiLevelProvidedParameter.success | 2 +- .../test-cases/simpleNoParameters.success | 2 +- .../simpleProvidedParameter.success | 2 +- .../test-cases/simpleUsingCompanion.success | 2 +- .../test-cases/subtypeParameter.success | 2 +- .../test-cases/taggingParameters.success | 2 +- .../todo/chainingFactoryMethods.success | 25 +++++++++++++++++++ .../todo/factoryMethodWithWiredParam.success | 0 .../todo/factoryWithParameterFromIO.success | 2 +- .../factoryWithParameterFromResource.success | 2 +- .../CompileTests.scala | 2 +- 23 files changed, 47 insertions(+), 22 deletions(-) rename macrosAutoCats/src/main/scala/com/softwaremill/macwire/{auto/catssupport => autocats}/MacwireAutoCatsMacros.scala (99%) rename macrosAutoCats/src/main/scala/com/softwaremill/macwire/{auto/catssupport => autocats}/package.scala (65%) create mode 100644 macrosAutoCatsTests/src/test/resources/test-cases/todo/chainingFactoryMethods.success create mode 100644 macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryMethodWithWiredParam.success rename macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/{auto/catssupport => autocats}/CompileTests.scala (90%) diff --git a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireAutoCatsMacros.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala similarity index 99% rename from macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireAutoCatsMacros.scala rename to macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala index 63d8701d..12132d0a 100644 --- a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/MacwireAutoCatsMacros.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/MacwireAutoCatsMacros.scala @@ -1,4 +1,4 @@ -package com.softwaremill.macwire.auto.catssupport +package com.softwaremill.macwire.autocats import scala.reflect.macros.blackbox import cats.effect.{IO, Resource => CatsResource} diff --git a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/package.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/package.scala similarity index 65% rename from macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/package.scala rename to macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/package.scala index 31853289..ec07dd63 100644 --- a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/auto/catssupport/package.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/package.scala @@ -1,7 +1,7 @@ -package com.softwaremill.macwire.auto +package com.softwaremill.macwire import cats.effect._ -package object catssupport { +package object autocats { def autowire[T](dependencies: Any*): Resource[IO, T] = macro MacwireCatsEffectMacros.autowire_impl[T] } \ No newline at end of file diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/factoryNoParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/factoryNoParameters.success index c1ccbadc..73383abd 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/factoryNoParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/factoryNoParameters.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameter.success index 4ae375c3..688d08c7 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameter.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/factoryWithParameter.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/ioProvidedParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/ioProvidedParameter.success index cdbe3e1c..18c8a672 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/ioProvidedParameter.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/ioProvidedParameter.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/resourceFactoryWithParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/resourceFactoryWithParameters.success index b816986d..b30b46e7 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/resourceFactoryWithParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/resourceFactoryWithParameters.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success index b7924c36..1e7e8536 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/resourceMultiLevelProvidedParameter.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/resourceProvidedParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/resourceProvidedParameters.success index 2618cccb..af5520ad 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/resourceProvidedParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/resourceProvidedParameters.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/simpleAutoParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/simpleAutoParameters.success index c4caacfb..dc2039ab 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/simpleAutoParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleAutoParameters.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDeps.failure b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDeps.failure index 7cb4d1e3..d4176061 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDeps.failure +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDeps.failure @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ import cats.effect._ class B(s: String) diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure index fbfd7143..c4193652 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDepsMultiLevel.failure @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ import cats.effect._ class A(s: String) diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success index d720bce3..b2ca6ff5 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelNoParameters.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success index 1db66377..f2de5f63 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMultiLevelProvidedParameter.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/simpleNoParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/simpleNoParameters.success index 47b4cc0f..3d5cef94 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/simpleNoParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleNoParameters.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/simpleProvidedParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/simpleProvidedParameter.success index 0670912b..4bce7229 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/simpleProvidedParameter.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleProvidedParameter.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/simpleUsingCompanion.success b/macrosAutoCatsTests/src/test/resources/test-cases/simpleUsingCompanion.success index 3de25186..f2698259 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/simpleUsingCompanion.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleUsingCompanion.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ import cats.effect._ object Test { diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/subtypeParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/subtypeParameter.success index f49799b8..4245d0e0 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/subtypeParameter.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/subtypeParameter.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/taggingParameters.success b/macrosAutoCatsTests/src/test/resources/test-cases/taggingParameters.success index 69450571..9562e2d2 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/taggingParameters.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/taggingParameters.success @@ -1,6 +1,6 @@ import cats.effect._ import com.softwaremill.tagging._ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ class Cache { val state = scala.collection.mutable.HashMap.empty[String, String] diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/todo/chainingFactoryMethods.success b/macrosAutoCatsTests/src/test/resources/test-cases/todo/chainingFactoryMethods.success new file mode 100644 index 00000000..a7e1003a --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/todo/chainingFactoryMethods.success @@ -0,0 +1,25 @@ +import com.softwaremill.macwire.autocats._ + +#include commonSimpleClasses +import cats.effect._ + + +val created = scala.collection.mutable.Set[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()) } + val theC: Resource[IO, C] = autowire[C](theA _, theB _) +} + +val theC: C = { + import cats.effect.unsafe.implicits.global + Test.theC.allocated.unsafeRunSync()._1 +} + +require(theC.a != null) +require(theC.b != null) + +require(created.size == 2) +require(created.contains("a")) +require(created.contains("B")) diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryMethodWithWiredParam.success b/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryMethodWithWiredParam.success new file mode 100644 index 00000000..e69de29b diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromIO.success b/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromIO.success index 57ad563b..7e1f98d6 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromIO.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromIO.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success b/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success index 61cf36eb..58ea9b0b 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success +++ b/macrosAutoCatsTests/src/test/resources/test-cases/todo/factoryWithParameterFromResource.success @@ -1,4 +1,4 @@ -import com.softwaremill.macwire.auto.catssupport._ +import com.softwaremill.macwire.autocats._ #include commonSimpleClasses import cats.effect._ diff --git a/macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/auto/catssupport/CompileTests.scala b/macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/autocats/CompileTests.scala similarity index 90% rename from macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/auto/catssupport/CompileTests.scala rename to macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/autocats/CompileTests.scala index 5d1fb597..01a13ea9 100644 --- a/macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/auto/catssupport/CompileTests.scala +++ b/macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/autocats/CompileTests.scala @@ -1,4 +1,4 @@ -package com.softwaremill.macwire.auto.catssupport +package com.softwaremill.macwire.autocats import com.softwaremill.macwire.CompileTestsSupport From 0446fc3d5095db745e7aa0705416e9123750dc1a Mon Sep 17 00:00:00 2001 From: Mateusz Borek Date: Thu, 21 Oct 2021 18:22:16 +0200 Subject: [PATCH 32/32] Add subtype test for resource --- .../subtypeResourceParameter.success | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 macrosAutoCatsTests/src/test/resources/test-cases/subtypeResourceParameter.success diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/subtypeResourceParameter.success b/macrosAutoCatsTests/src/test/resources/test-cases/subtypeResourceParameter.success new file mode 100644 index 00000000..234d64da --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/subtypeResourceParameter.success @@ -0,0 +1,27 @@ +import com.softwaremill.macwire.autocats._ + +import cats.effect._ + +trait A +class AA(i: Int) extends A + +class B(val a: A) + +object Test { + + val theAA: Resource[IO, AA] = Resource.pure(new AA(1)) + val theB: B = { + import cats.effect.unsafe.implicits.global + + val resource = autowire[B](theAA) + resource.allocated.unsafeRunSync()._1 + } + +} + +val theAaValue = { + import cats.effect.unsafe.implicits.global + + Test.theAA.allocated.unsafeRunSync()._1 +} +require(Test.theB.a == theAaValue)