Skip to content

Commit

Permalink
fix ambiguity checks in ZLayer#provide macro
Browse files Browse the repository at this point in the history
  • Loading branch information
myazinn committed Dec 20, 2023
1 parent e48095b commit e2936bb
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ private[zio] trait LayerMacroUtils {
providedLayers0 = layers.toList,
layerToDebug = debugMap,
sideEffectType = c.weakTypeOf[Unit].dealias,
anyType = c.weakTypeOf[Any].dealias,
typeEquals = _ <:< _,
foldTree = buildFinalTree,
method = provideMethod,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ private [zio] object LayerMacroUtils {
layerToDebug = layerToDebug,
typeEquals = _ <:< _,
sideEffectType = TypeRepr.of[Unit],
anyType = TypeRepr.of[Any],
foldTree = buildFinalTree,
method = provideMethod,
exprToNode = getNode,
Expand Down
41 changes: 36 additions & 5 deletions core/shared/src/main/scala/zio/internal/macros/LayerBuilder.scala
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ final case class LayerBuilder[Type, Expr](
providedLayers0: List[Expr],
layerToDebug: PartialFunction[Expr, Debug],
sideEffectType: Type,
anyType: Type,
typeEquals: (Type, Type) => Boolean,
foldTree: LayerTree[Expr] => Expr,
method: ProvideMethod,
Expand Down Expand Up @@ -114,13 +115,28 @@ final case class LayerBuilder[Type, Expr](
* confusing the user and breaking stuff.
*/
private def assertNoAmbiguity(): Unit = {
val typesToExprs: Map[String, List[String]] =
groupMap(providedLayerNodes.flatMap { node =>
node.outputs.map(output => showType(output) -> showExpr(node.value))
})(_._1)(_._2)
val simpleDuplicates: Map[String, List[String]] =
groupMap {
providedLayerNodes.flatMap { node =>
node.outputs.map(output => showType(output) -> showExpr(node.value))
}
}(_._1)(_._2)

val subtypingDuplicates: Map[String, List[String]] =
groupMap {
distinctBy {
(target ++ providedLayerNodes.flatMap(_.inputs))
.filterNot(typeEquals(anyType, _))
.map(tpe => tpe -> showType(tpe))
}(_._2).flatMap { case (tpe, prettyTpe) =>
providedLayerNodes.flatMap { node =>
node.outputs.collect { case output if typeEquals(output, tpe) => showExpr(node.value) }
}.map(prettyTpe -> _)
}
}(_._1)(_._2)

val duplicates: List[(String, List[String])] =
typesToExprs.toList.filter { case (_, list) => list.size > 1 }
(simpleDuplicates ++ subtypingDuplicates).toList.filter { case (_, list) => list.size > 1 }

if (duplicates.nonEmpty) {
val message = "\n" + TerminalRendering.ambiguousLayersError(duplicates)
Expand Down Expand Up @@ -317,6 +333,21 @@ final case class LayerBuilder[Type, Expr](
result
}

// Backwards compatibility for 2.12
private def distinctBy[A, B](as: List[A])(f: A => B): List[A] =
if (as.lengthCompare(1) <= 0) as
else {
val builder = new ListBuffer[A]
val seen = mutable.HashSet.empty[B]
val it = as.iterator
var different = false
while (it.hasNext) {
val next = it.next()
if (seen.add(f(next))) builder += next else different = true
}
if (different) builder.result() else as
}

final case class MermaidGraph(
topLevel: Chunk[String],
deps: Map[String, Chunk[String]]
Expand Down
61 changes: 61 additions & 0 deletions test-tests/shared/src/test/scala/zio/test/TestProvideSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,42 @@ object TestProvideSpec extends ZIOBaseSpec {
}
.provide(doubleLayer, stringLayer, intLayer)
},
test("reports ambiguous layers") {
import TestLayer._
val gp: ULayer[Repository] = GreenplumRepository.live
val mongo: ULayer[Repository] = MongoRepository.live

val program: URIO[Repository, String] = ZIO.succeed("test")
val _ = (gp, mongo, program)

val checked = typeCheck("""test("foo")(assertZIO(program)(anything)).provide(gp, mongo)""")
assertZIO(checked)(isLeft(containsStringWithoutAnsi("Ambiguous layers")))
} @@ TestAspect.exceptScala3,
test("reports ambiguous layers which are subtypes of some requirement") {
import TestLayer._
val gp = GreenplumRepository.live
val mongo = MongoRepository.live
val repo = RepositoryLive.live

val program: URIO[Repository, String] = ZIO.succeed("test")
val _ = (gp, mongo, repo, program)

val checked = typeCheck("""test("foo")(assertZIO(program)(anything)).provide(repo, gp, mongo)""")
assertZIO(checked)(isLeft(containsStringWithoutAnsi("Ambiguous layers")))
} @@ TestAspect.exceptScala3,
test("reports ambiguous layers which are subtypes of some transitive requirement") {
import TestLayer._
val gp = GreenplumRepository.live
val mongo = MongoRepository.live
val repo = RepositoryLive.live

val program: URIO[Service, String] = ZIO.succeed("test")
val _ = (gp, mongo, repo, program)

val checked =
typeCheck("""test("foo")(assertZIO(program)(anything)).provide(ServiceLive.live, repo, gp, mongo)""")
assertZIO(checked)(isLeft(containsStringWithoutAnsi("Ambiguous layers")))
} @@ TestAspect.exceptScala3,
test("reports missing top-level dependencies") {
val program: URIO[String with Int, String] = ZIO.succeed("test")
val _ = program
Expand Down Expand Up @@ -173,6 +209,31 @@ object TestProvideSpec extends ZIOBaseSpec {
object Spider {
def live: ULayer[Spider] = ZLayer.succeed(new Spider {})
}

trait Repository

case class MongoRepository() extends Repository
object MongoRepository {
val live: ULayer[MongoRepository] = ZLayer.fromFunction(MongoRepository.apply _)
}

case class GreenplumRepository() extends Repository
object GreenplumRepository {
val live: ULayer[GreenplumRepository] = ZLayer.fromFunction(GreenplumRepository.apply _)
}

case class RepositoryLive(mongo: MongoRepository, gp: GreenplumRepository) extends Repository
object RepositoryLive {
val live: URLayer[MongoRepository with GreenplumRepository, RepositoryLive] =
ZLayer.fromFunction(RepositoryLive.apply _)
}

trait Service
case class ServiceLive(repo: Repository) extends Service
object ServiceLive {
val live: URLayer[Repository, ServiceLive] = ZLayer.fromFunction(ServiceLive.apply _)
}

}

}

0 comments on commit e2936bb

Please sign in to comment.