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 2783cc68..c37c600b 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 @@ -72,7 +72,7 @@ object CompanionCrimper { def applyFactory[C <: blackbox.Context]( c: C, log: Logger - )(targetType: c.Type): Option[(List[List[c.Symbol]], List[List[c.Tree]] => c.Tree)] = { + )(targetType: c.Type): Option[(c.Symbol, List[List[c.Symbol]], List[List[c.Tree]] => c.Tree)] = { import c.universe._ lazy val apply: Option[Symbol] = CompanionCrimper @@ -92,7 +92,8 @@ object CompanionCrimper { for { params <- applyParamLists applyMethod <- applySelect - } yield (params, factory(applyMethod)(_)) + a <- apply + } yield (a, params, factory(applyMethod)(_)) } } 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 d93630d7..b0aecc65 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 @@ -101,7 +101,7 @@ object ConstructorCrimper { c: C, log: Logger )(targetType: c.Type, resolver: (c.Symbol, c.Type) => c.Tree): Option[c.Tree] = - constructorFactory(c, log)(targetType).map { case (paramLists, factory) => + constructorFactory(c, log)(targetType).map { case (_, paramLists, factory) => import c.universe._ lazy val targetTypeD: Type = targetType.dealias @@ -119,7 +119,7 @@ object ConstructorCrimper { def constructorFactory[C <: blackbox.Context]( c: C, log: Logger - )(targetType: c.Type): Option[(List[List[c.Symbol]], List[List[c.Tree]] => c.Tree)] = { + )(targetType: c.Type): Option[(c.Symbol, List[List[c.Symbol]], List[List[c.Tree]] => c.Tree)] = { import c.universe._ lazy val targetTypeD: Type = targetType.dealias @@ -128,13 +128,17 @@ object ConstructorCrimper { val constructorParamLists: Option[List[List[Symbol]]] = constructor.map(_.asMethod.paramLists.filterNot(_.headOption.exists(_.isImplicit))) + + val constructionMethodTree: Tree = Select(New(Ident(targetTypeD.typeSymbol)), termNames.CONSTRUCTOR) def factory(constructorArgs: List[List[Tree]]): Tree = { - val constructionMethodTree: Tree = Select(New(Ident(targetTypeD.typeSymbol)), termNames.CONSTRUCTOR) constructorArgs.foldLeft(constructionMethodTree)((acc: Tree, args: List[Tree]) => Apply(acc, args)) } - constructorParamLists.map(cpl => (cpl, factory(_))) + for { + cpl <- constructorParamLists + c <- constructor + } yield (c, cpl, factory(_)) } } diff --git a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/internals/CatsProviders.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/internals/CatsProviders.scala index e9132f1a..e48d76ba 100644 --- a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/internals/CatsProviders.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/internals/CatsProviders.scala @@ -11,16 +11,20 @@ trait CatsProviders[C <: blackbox.Context] { sealed trait Provider { def resultType: c.Type - def dependencies: List[List[Option[Provider]]] + def dependencies: List[List[(c.Symbol, Provider)]] def ident: c.Tree def value: c.Tree + def symbol: c.Symbol } class Effect(rawValue: c.Tree) extends Provider { + + override def symbol: c.Symbol = rawValue.symbol + import c.universe._ lazy val resultType: Type = Effect.underlyingType(typeCheckUtil.typeCheckIfNeeded(rawValue)) - lazy val dependencies: List[List[Option[Provider]]] = List.empty + lazy val dependencies: List[List[(c.Symbol, Provider)]] = List.empty lazy val asResource = new Resource(q"cats.effect.kernel.Resource.eval[cats.effect.IO, ${resultType}]($rawValue)") lazy val value: Tree = asResource.value lazy val ident: Tree = asResource.ident @@ -39,10 +43,13 @@ trait CatsProviders[C <: blackbox.Context] { } class Resource(val value: c.Tree) extends Provider { + + override def symbol: c.Symbol = value.symbol + import c.universe._ lazy val resultType: Type = Resource.underlyingType(typeCheckUtil.typeCheckIfNeeded(value)) - lazy val dependencies: List[List[Option[Provider]]] = List.empty + lazy val dependencies: List[List[(c.Symbol, Provider)]] = List.empty lazy val ident: Tree = Ident(TermName(c.freshName())) } @@ -62,17 +69,16 @@ trait CatsProviders[C <: blackbox.Context] { } class FactoryMethod( + val symbol: c.Symbol, methodType: c.Type, val resultType: c.Type, - val dependencies: List[List[Option[Provider]]], + val dependencies: List[List[(c.Symbol, Provider)]], apply: List[List[c.Tree]] => c.Tree ) extends Provider { import c.universe._ - private lazy val appliedTree: Tree = apply(dependencies.map(_.map(_.get.ident))) - // log.withBlock(s"Applied tree for [$fun] from deps: [${dependencies.map(_.mkString(", ")).mkString(", ")}]") { - // dependencies.map(_.map(_.get.ident)).foldLeft(fun)((acc: Tree, args: List[Tree]) => Apply(acc, args)) - // } + private lazy val appliedTree: Tree = apply(dependencies.map(_.map(_._2.ident))) + lazy val result: Provider = log.withResult { val fmResultType = resultType //TODO support for FactoryMethods @@ -127,10 +133,21 @@ trait CatsProviders[C <: blackbox.Context] { class Instance(val value: c.Tree) extends Provider { - override def dependencies: List[List[Option[Provider]]] = List.empty + override def symbol: c.Symbol = value.symbol + + + override def dependencies: List[List[(c.Symbol, Provider)]] = List.empty lazy val resultType: c.Type = typeCheckUtil.typeCheckIfNeeded(value) lazy val ident: c.Tree = value } + + class NotResolvedProvider(val resultType: c.Type, val symbol: c.Symbol) extends Provider { + override def dependencies: List[List[(c.Symbol, Provider)]] = c.abort(c.enclosingPosition, s"Internal Error: Not resolved provider for type [$resultType]") + override def ident: c.Tree = c.abort(c.enclosingPosition, s"Internal Error: Not resolved provider for type [$resultType]") + override def value: c.Tree = c.abort(c.enclosingPosition, s"Internal Error: Not resolved provider for type [$resultType]") + + + } } diff --git a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/internals/CatsProvidersGraphContext.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/internals/CatsProvidersGraphContext.scala index 1ecf283a..2ccfa71b 100644 --- a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/internals/CatsProvidersGraphContext.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/internals/CatsProvidersGraphContext.scala @@ -7,17 +7,17 @@ import cats.implicits._ class CatsProvidersGraphContext[C <: blackbox.Context](val c: C, val log: Logger) extends CatsProviders[C] with GraphBuilderUtils[C] { + + case class Param(symbol: c.Symbol, tpe: c.Type) lazy val typeCheckUtil = new TypeCheckUtil[c.type](c, log) class CatsProvidersGraph(providers: List[Provider], val root: Provider) { log(s"Created graph for providers [${mkStringProviders(providers)}]") - private def verifyOrder(resultProviders: List[Provider]) = + private def verifyOrder(resultProviders: List[Provider]) = { log.withBlock(s"Verifying order of [${mkStringProviders(resultProviders)}]") { def go(provider: Provider): Set[Provider] = - provider.dependencies.flatten.foldl(Set(provider))((result, maybeProvider) => - maybeProvider.map(go).getOrElse(Set.empty) ++ result - ) + provider.dependencies.flatten.foldl(Set(provider)) { case (result, (_, dep)) => go(dep) ++ result } resultProviders.lastOption.map(go).map { usedProviders => val notUsedProviders = providers.diff(usedProviders.toSeq) @@ -29,16 +29,62 @@ class CatsProvidersGraphContext[C <: blackbox.Context](val c: C, val log: Logger ) } } + } + + private def failOnMissingDependencies() = log.withBlock("Checking missing dependencies"){ + type ProviderPath = List[(c.Symbol, Provider)] + case class CheckContext(currentPath: ProviderPath, missingPaths: Set[ProviderPath]) { + def withProvider(sym: c.Symbol, provider: Provider)(f: CheckContext => CheckContext) = { + val currentCtx = copy(currentPath = currentPath.:+((sym, provider))) + val processedCtx = f(currentCtx) + copy(missingPaths = processedCtx.missingPaths) + } + + def missingPath(): CheckContext = copy(missingPaths = missingPaths.+(currentPath)) + } + + object CheckContext { + lazy val empty = CheckContext(List.empty, Set.empty) + } + + def go(ctx: CheckContext)(sym: c.Symbol, provider: Provider): CheckContext = log.withBlock(s"Checking provider [$provider] for type [${provider.resultType}]") { + ctx.withProvider(sym, provider) { currentCtx => + provider match { + case _: NotResolvedProvider => currentCtx.missingPath() + case _ => provider.dependencies.foldLeft(currentCtx) { case (paramsCtx, deps) => deps.foldLeft(paramsCtx) { case (paramCtx, (sym, param)) => go(paramCtx)(sym, param)}} + } + } + } + + val resultCtx = go(CheckContext.empty)(c.universe.NoSymbol, root) + + if (resultCtx.missingPaths.nonEmpty) { + def buildPathMsg(path: ProviderPath) = if (path.size <= 1) path.map(_._1).mkString + else { + val head = path.head._2 + val mid = path.drop(1).dropRight(1) + val last = path.last._2 + + val midStr = mid.map { case (sym, provider) => s".${sym.name} -> [${provider.symbol}]"}.mkString("", "", "") + + s"Missing dependency of type [${last.resultType}]. Path [${head.symbol}]$midStr.${last.symbol.name}" + } + + val msg = resultCtx.missingPaths.map(buildPathMsg).mkString(s"Failed to create an instance of [${root.resultType}].\n", "\n", "\n") + + c.error(c.enclosingPosition, msg) + } + } def topologicalSort(): List[Provider] = log.withBlock("Stable topological sort") { def go(provider: Provider, usedProviders: Set[Provider]): List[Provider] = log.withBlock(s"Going deeper for type [${provider.resultType}]") { provider.dependencies.flatten.foldl(List.empty[Provider]) { - case (_, None) => c.abort(c.enclosingPosition, "Missing dependency.") - case (r, Some(p)) if (usedProviders ++ r.toSet).contains(p) => + case (r, (_, p: NotResolvedProvider)) => log.withBlock(s"Skipping not resolved provider for type [${p.resultType}]")(r) + case (r, (_, p)) if (usedProviders ++ r.toSet).contains(p) => log.withBlock(s"Already used provider for type [${p.resultType}]")(r) - case (r, Some(p)) => + case (r, (_, p)) => log.withResult { (r ::: go(p, (usedProviders ++ r.toSet) + p)) :+ p }(result => s"Built list for the following types [${result.map(_.resultType).mkString(", ")}]" ) @@ -49,7 +95,8 @@ class CatsProvidersGraphContext[C <: blackbox.Context](val c: C, val log: Logger case (resultProviders, nextProvider) if resultProviders.contains(nextProvider) => resultProviders case (resultProviders, nextProvider) => resultProviders ::: go(nextProvider, resultProviders.toSet) } :+ root - + + failOnMissingDependencies() verifyOrder(result) result @@ -90,8 +137,14 @@ class CatsProvidersGraphContext[C <: blackbox.Context](val c: C, val log: Logger /** We assume that we cannot use input provider directly, so we create a result object with available constructors. * It's a mimic of `wire`'s property */ - val (resolvedCtx, rootProvider) = (maybeResolveWithFactoryMethod(resolvedFMContext)(rootType) orElse maybeResolveParamWithCreator(resolvedFMContext)(rootType)) - .getOrElse(c.abort(c.enclosingPosition, s"Cannot construct an instance of type: [$rootType]")) + + val (resolvedCtx, rootProvider) = maybeResolveWithFactoryMethod(resolvedFMContext)( + rootType + ).getOrElse( + findResolvableCreator(resolvedFMContext)(rootType).getOrElse( + c.abort(c.enclosingPosition, s"Cannot construct an instance of type: [$rootType]") + ) + ) val inputProvidersTypes = inputProviders .map { @@ -106,8 +159,7 @@ class CatsProvidersGraphContext[C <: blackbox.Context](val c: C, val log: Logger new CatsProvidersGraph(sortedProviders, rootProvider) } - /** - * Make sure that we compose resources in the same order we received them + /** Make sure that we compose resources in the same order we received them */ private def sortProvidersWithInputOrder( inputProvidersTypes: List[c.Type] @@ -119,64 +171,77 @@ class CatsProvidersGraphContext[C <: blackbox.Context](val c: C, val log: Logger inputProviders ++ resultContext.providers.diff(inputProviders) } - private def maybeResolveWithFactoryMethod(ctx: BuilderContext)(param: c.Type): Option[(BuilderContext, Provider)] = ctx.providers.find { - case fm: FactoryMethod => fm.resultType <:< param - case _ => false - }.map(r => (ctx, r)) + private def maybeResolveWithFactoryMethod(ctx: BuilderContext)(param: c.Type): Option[(BuilderContext, Provider)] = + ctx.providers + .find { + case fm: FactoryMethod => fm.resultType <:< param + case _ => false + } + .map(r => (ctx, r)) - private def maybeResolveParamWithCreator(ctx: BuilderContext)(param: c.Type): Option[(BuilderContext, FactoryMethod)] = - log.withBlock(s"Resolving creator for [$param]") { - def maybeResolveParams( - maybeFactory: Option[(List[List[c.Symbol]], List[List[c.Tree]] => c.Tree)] - ): Option[(BuilderContext, FactoryMethod)] = { - maybeFactory.flatMap { case (creatorParams, creatorF) => - val paramsTypes = creatorParams.map(_.map(paramType(c)(param, _))) - log.trace(s"Creator params [${paramsTypes.mkString(", ")}] for type [$param]") + type CreatorType = (c.Symbol, List[List[c.Symbol]], List[List[c.Tree]] => c.Tree) - val (updatedCtx, resolvedConParams) = resolveParamsLists(ctx)(paramsTypes) + private def findResolvableCreator(ctx: BuilderContext)(param: c.Type): Option[(BuilderContext, Provider)] = { + val maybeConstructor = ConstructorCrimper.constructorFactory(c, log)(param).map(resolveCreatorParams(ctx)(param, _)) + val maybeApply = CompanionCrimper.applyFactory(c, log)(param).map(resolveCreatorParams(ctx)(param, _)) - if (resolvedConParams.exists(_.exists(_.isEmpty))) None - else { - val creator = new FactoryMethod(param, param, resolvedConParams, creatorF) - Some((updatedCtx.addProvider(creator), creator)) - } - } + def allDependenciesResolved(result: (BuilderContext, FactoryMethod)): Boolean = result._2.dependencies.flatten.collectFirst{case t@(_, _: NotResolvedProvider) => t}.isEmpty - } + (maybeConstructor, maybeApply) match { + //Found at least one resolvable creator + case (Some(result), _) if allDependenciesResolved(result) => Some(result) + case (_, Some(result)) if allDependenciesResolved(result) => Some(result) - if (!isWireable(c)(param)) None - else - maybeResolveParams(ConstructorCrimper.constructorFactory(c, log)(param)) - .orElse(maybeResolveParams(CompanionCrimper.applyFactory(c, log)(param))) + //Found at least one non-resolvable creator + case (Some(result), _) => Some(result) + case (None, Some(result)) => Some(result) + //Not found any creator + case (None, None) => None } + } - private def resolveParamsList(ctx: BuilderContext)(params: List[c.Type]): (BuilderContext, List[Option[Provider]]) = - params.foldLeft((ctx, List.empty[Option[Provider]])) { case ((currentCtx, resolvedParams), param) => - currentCtx.resolve(param) match { - case Some(Left(provider)) => (currentCtx, resolvedParams :+ Some(provider)) + private def resolveCreatorParams( + ctx: BuilderContext + )(param: c.Type, creator: CreatorType): (BuilderContext, FactoryMethod) = { + val (methodSymbol, creatorParams, creatorF) = creator + + val paramsTypes = creatorParams.map(_.map(paramSym => Param(paramSym, paramType(c)(param, paramSym)))) + log.trace(s"Creator params [${paramsTypes.mkString(", ")}] for type [$param]") + + val (updatedCtx, resolvedConParams) = resolveParamsLists(ctx)(paramsTypes) + + val fm = new FactoryMethod(methodSymbol, param, param, resolvedConParams, creatorF) + (updatedCtx.addProvider(fm), fm) + } + + private def resolveParamsList(ctx: BuilderContext)(params: List[Param]): (BuilderContext, List[(c.Symbol, Provider)]) = + params.foldLeft((ctx, List.empty[(c.Symbol, Provider)])) { case ((currentCtx, resolvedParams), Param(paramSym, paramTpe)) => + currentCtx.resolve(paramTpe) match { + case Some(Left(provider)) => (currentCtx, resolvedParams.:+((paramSym, provider))) case Some(Right(fmt)) => { val (updatedCtx, fm) = resolveFactoryMethod(currentCtx)(fmt) - (updatedCtx, resolvedParams :+ Some(fm)) + (updatedCtx, resolvedParams.:+((paramSym, fm))) } - case None => - maybeResolveParamWithCreator(currentCtx)(param) match { - case Some((updatedCtx, creator)) => - (updatedCtx, resolvedParams :+ Some(creator)) - case None => (ctx, resolvedParams :+ None) - } + case None if isWireable(c)(paramTpe) => + findResolvableCreator(currentCtx)(paramTpe).map { case (updatedCtx, fm) => + updatedCtx.logContext + (updatedCtx, resolvedParams.:+((paramSym, fm))) + }.getOrElse(((ctx, resolvedParams.:+((paramSym, new NotResolvedProvider(paramTpe, paramSym)))))) + case _ => (ctx, resolvedParams.:+((paramSym, new NotResolvedProvider(paramTpe, paramSym)))) } } + + private def resolveParamsLists( ctx: BuilderContext - )(params: List[List[c.Type]]): (BuilderContext, List[List[Option[Provider]]]) = + )(params: List[List[Param]]): (BuilderContext, List[List[(c.Symbol, Provider)]]) = log.withBlock(s"Resolving params [${mkStringFrom2DimList(params)}]") { - params.foldLeft((ctx, List.empty[List[Option[Provider]]])) { - case ((currentCtx, resolvedParamsLists), paramsList) => - val (updatedCtx, resolvedParamsList) = resolveParamsList(currentCtx)(paramsList) - (updatedCtx, resolvedParamsLists :+ resolvedParamsList) + params.foldLeft((ctx, List.empty[List[(c.Symbol, Provider)]])) { case ((currentCtx, resolvedParamsLists), paramsList) => + val (updatedCtx, resolvedParamsList) = resolveParamsList(currentCtx)(paramsList) + (updatedCtx, resolvedParamsLists :+ resolvedParamsList) } } @@ -186,12 +251,15 @@ class CatsProvidersGraphContext[C <: blackbox.Context](val c: C, val log: Logger val FactoryMethodTree(params, fun, resultType) = fmt log.withBlock(s"Resolving factory method [$fun]") { - val paramsTypes = params.map { case ValDef(_, name, tpt, rhs) => typeCheckUtil.typeCheckIfNeeded(tpt) } + val paramsTypes = params.map { case s @ ValDef(_, name, tpt, rhs) => + Param(s.symbol, typeCheckUtil.typeCheckIfNeeded(tpt)) + } val (updatedCtx, deps) = resolveParamsLists(ctx)(List(paramsTypes)) log(s"Resolved dependencies [${deps.map(_.mkString(", ")).mkString("\n")}]") val fm = new FactoryMethod( + symbol = fun.symbol, methodType = fun.symbol.asMethod.returnType, resultType = resultType, dependencies = deps, @@ -204,8 +272,7 @@ class CatsProvidersGraphContext[C <: blackbox.Context](val c: C, val log: Logger } } - /** - * DFS based algorithm that resolves all `FactoryMethodTree` + /** DFS based algorithm that resolves all `FactoryMethodTree` */ private def resolveFactoryMethods(ctx: BuilderContext): BuilderContext = ctx.next() match { case None => ctx diff --git a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/internals/GraphBuilderUtils.scala b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/internals/GraphBuilderUtils.scala index 7a50ac90..3175254a 100644 --- a/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/internals/GraphBuilderUtils.scala +++ b/macrosAutoCats/src/main/scala/com/softwaremill/macwire/autocats/internals/GraphBuilderUtils.scala @@ -28,12 +28,14 @@ trait GraphBuilderUtils[C <: blackbox.Context] { this: CatsProviders[C] => ) { import c.universe._ + def logContext = log(s"Available instances in context: [${providers.map(_.resultType)}] & [${notResolvedFactoryMethods.map(_.resultType)}]") def resolvedFactoryMethod(provider: FactoryMethod): BuilderContext = copy( providers = provider :: providers, notResolvedFactoryMethods = notResolvedFactoryMethods.filter(p => p.resultType != provider.resultType) ) def resolve(tpe: Type): Option[Either[Provider, FactoryMethodTree]] = { + logContext val resolvedProviders = providers .filter(_.resultType <:< tpe) .map(_.asLeft[FactoryMethodTree]) diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/constructInputProvider.failure b/macrosAutoCatsTests/src/test/resources/test-cases/constructInputProvider.failure index 42476ae3..5565ec53 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/constructInputProvider.failure +++ b/macrosAutoCatsTests/src/test/resources/test-cases/constructInputProvider.failure @@ -1,11 +1,11 @@ - class A(s: String) +class A(s: String) +class B(a: A) - object Test { - val theA = autowire[A](new A("s")) +object Test { + val theB = autowire[B](new B(new A("s"))) +} - } - - val theA: A = { - import cats.effect.unsafe.implicits.global - Test.theA.allocated.unsafeRunSync()._1 - } \ No newline at end of file +val theA: A = { + import cats.effect.unsafe.implicits.global + Test.theA.allocated.unsafeRunSync()._1 +} \ No newline at end of file diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/missingApplyDependencies.failure b/macrosAutoCatsTests/src/test/resources/test-cases/missingApplyDependencies.failure new file mode 100644 index 00000000..4f6ed4d8 --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/missingApplyDependencies.failure @@ -0,0 +1,10 @@ +object Test { + class A() + class B private (a: A) + object B { + def apply(a:A, i: Int) = new B(a) + } + class C(b: B) + + val theC: Resource[IO, C] = autowire[C]() +} \ No newline at end of file diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/missingConstructorDependencies.failure b/macrosAutoCatsTests/src/test/resources/test-cases/missingConstructorDependencies.failure new file mode 100644 index 00000000..7076b714 --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/missingConstructorDependencies.failure @@ -0,0 +1,7 @@ +class A() +class B(a: A, i: Int) +class C(b: B) + +object Test { + val theC: Resource[IO, C] = autowire[C]() +} \ No newline at end of file diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/missingFactoryMethodDependencies.failure b/macrosAutoCatsTests/src/test/resources/test-cases/missingFactoryMethodDependencies.failure new file mode 100644 index 00000000..18426e70 --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/missingFactoryMethodDependencies.failure @@ -0,0 +1,9 @@ +class A() +class B(a: A) +class C(b: B) + +object Test { + def makeB(a: A, i: Int) = new B(a) + + val theC: Resource[IO, C] = autowire[C](makeB _) +} \ No newline at end of file diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/missingMultipleConstructorDependencies.failure b/macrosAutoCatsTests/src/test/resources/test-cases/missingMultipleConstructorDependencies.failure new file mode 100644 index 00000000..05632f72 --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/missingMultipleConstructorDependencies.failure @@ -0,0 +1,7 @@ +class A() +class B(a: A, i: Int) +class C(b: B, s: String) + +object Test { + val theC: Resource[IO, C] = autowire[C]() +} \ No newline at end of file diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDeps.failure b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDeps.failure index 6f241c44..ef7383f3 100644 --- a/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDeps.failure +++ b/macrosAutoCatsTests/src/test/resources/test-cases/simpleMissingDeps.failure @@ -1,4 +1,5 @@ -class B(s: String) +class A(s: String) +class B(a: A) object Test { val theB: Resource[IO, B] = autowire[B]() diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/todo/autowireTuples.success b/macrosAutoCatsTests/src/test/resources/test-cases/todo/autowireTuples.success new file mode 100644 index 00000000..869979cc --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/todo/autowireTuples.success @@ -0,0 +1,13 @@ +#include commonSimpleClasses + +object Test { + val tuple = autowire[(A, B)]() +} + +val tuple: (A, B) = { + import cats.effect.unsafe.implicits.global + Test.tuple.allocated.unsafeRunSync()._1 +} + +require(tuple._1 == A()) +require(tuple._2 == B()) \ No newline at end of file diff --git a/macrosAutoCatsTests/src/test/resources/test-cases/todo/creatorBacktracking.success b/macrosAutoCatsTests/src/test/resources/test-cases/todo/creatorBacktracking.success new file mode 100644 index 00000000..9943bed6 --- /dev/null +++ b/macrosAutoCatsTests/src/test/resources/test-cases/todo/creatorBacktracking.success @@ -0,0 +1,25 @@ +val created = scala.collection.mutable.ListBuffer[Int]() + +class A() + +class B private (a: A) +object B { + def apply(a:A, i: Int) = new B(a) +} + +class C(b: B) +object C { + def apply(a: A) = { + created.append(1) + B(a, 1) + } +} + +class D(c: C) + +object Test { + val theD: Resource[IO, D] = autowire[D]() +} + +require(created.size == 1) +require(created(0) == 1) \ No newline at end of file diff --git a/macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/autocats/CompileTests.scala b/macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/autocats/CompileTests.scala index 8acc0ed9..440ea167 100644 --- a/macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/autocats/CompileTests.scala +++ b/macrosAutoCatsTests/src/test/scala/com/softwaremill/macwire/autocats/CompileTests.scala @@ -6,12 +6,16 @@ class CompileTests extends CatsAutowireCompileTestsSupport { runTestsWith( expectedFailures = List( - "simpleMissingDeps" -> List("Cannot construct an instance of type: [B]"), //TODO List("Cannot find a value of type: [String], path: B.s") - "simpleMissingMultiLevelDeps" -> List("Cannot construct an instance of type: [C]"), //TODO List("Cannot find a value of type: [String], path: C.b.a.s") + "simpleMissingDeps" -> List("Failed to create an instance of [B].", "Missing dependency of type [String]. Path [constructor B].a -> [constructor A].s"), + "simpleMissingMultiLevelDeps" -> List("Failed to create an instance of [C].", "Missing dependency of type [String]. Path [constructor C].b -> [constructor B].a -> [constructor A].s"), "notUsedProvider" -> List("Not used providers for the following types [D]"), "ambiguousInstances" -> List("Ambiguous instances of types [A]"), "ambiguousTraitImplementations" -> List("Ambiguous instances of types [A, B]"), - "constructInputProvider" -> List("Cannot construct an instance of type: [A]") + "constructInputProvider" -> List("Failed to create an instance of [B].", "Missing dependency of type [String]. Path [constructor B].a -> [constructor A].s"),//TODO we should add a warning in this case. + "missingConstructorDependencies" -> List("Failed to create an instance of [C].", "Missing dependency of type [Int]. Path [constructor C].b -> [constructor B].i"), + "missingMultipleConstructorDependencies" -> List("Failed to create an instance of [C].", "Missing dependency of type [Int]. Path [constructor C].b -> [constructor B].i", "Missing dependency of type [String]. Path [constructor C].s"), + "missingApplyDependencies" -> List("Failed to create an instance of [", "C].", "Missing dependency of type [Int]. Path [constructor C].b -> [method apply].i"), + "missingFactoryMethodDependencies" -> List("Failed to create an instance of [C].", "Missing dependency of type [Int]. Path [constructor C].b -> [method makeB].i"), ), expectedWarnings = List() )