Skip to content

Commit

Permalink
add support for ZLayer provide macro
Browse files Browse the repository at this point in the history
  • Loading branch information
myazinn committed Nov 30, 2023
1 parent 89b37e3 commit 7c48579
Show file tree
Hide file tree
Showing 4 changed files with 204 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import org.jetbrains.plugins.scala.lang.psi.types.api.ParameterizedType
import org.jetbrains.plugins.scala.lang.psi.types.result.Typeable
import org.jetbrains.plugins.scala.lang.psi.types.{api, ScType, TypePresentationContext}
import org.jetbrains.plugins.scala.project.{ProjectContext, ProjectPsiElementExt, ScalaFeatures}
import zio.intellij.inspections._
import zio.intellij.inspections.macros.LayerBuilder._
import zio.intellij.inspections.macros.LayerTree.{ComposeH, ComposeV, Empty, Value}
import zio.intellij.inspections.zioMethods._
Expand All @@ -35,23 +36,31 @@ class ProvideMacroInspection extends LocalInspectionTool {
case expr @ `.inject`(base, layers @ _*) =>
tryBuildProvideZIO1(base, layers).fold(visitIssue(holder, expr), identity)
case fullyApplied @ ScMethodCall(partiallyApplied @ `.injectSome`(base, _ @_*), layers) =>
visitProvideSomeZIO1(partiallyApplied, base, layers).fold(visitIssue(holder, fullyApplied), identity)
tryBuildProvideSomeZIO1(partiallyApplied, base, layers).fold(visitIssue(holder, fullyApplied), identity)
case expr @ `.injectShared`(base, layers @ _*) =>
tryBuildProvideZIO1(base, layers).fold(visitIssue(holder, expr), identity)
case fullyApplied @ ScMethodCall(partiallyApplied @ `.injectSomeShared`(base, _ @_*), layers) =>
visitProvideSomeSharedZIO1(partiallyApplied, base, layers).fold(visitIssue(holder, fullyApplied), identity)
tryBuildProvideSomeSharedZIO1(partiallyApplied, base, layers).fold(visitIssue(holder, fullyApplied), identity)
case fullyApplied @ ScMethodCall(partiallyApplied @ ScGenericCall(`ZLayer.makeLike`(_, _), _), layers) =>
tryBuildProvideZIO1(partiallyApplied, layers).fold(visitIssue(holder, fullyApplied), identity)
case fullyApplied @ ScMethodCall(partiallyApplied @ ScGenericCall(`ZLayer.makeSomeLike`(_, _), _), layers) =>
tryBuildProvideSomeZIO1(fullyApplied, partiallyApplied, layers).fold(visitIssue(holder, fullyApplied), identity)
case _ =>
}

private def visitZIO2ProvideMethods(holder: ProblemsHolder)(element: PsiElement): Unit = element match {
case expr @ `.provide`(base, layers @ _*) =>
tryBuildProvideZIO2(base, layers).fold(visitIssue(holder, expr), identity)
case fullyApplied @ ScMethodCall(partiallyApplied @ `.provideSome`(base, _ @_*), layers) =>
visitProvideSomeZIO2(partiallyApplied, base, layers).fold(visitIssue(holder, fullyApplied), identity)
tryBuildProvideSomeZIO2(partiallyApplied, base, layers).fold(visitIssue(holder, fullyApplied), identity)
case expr @ `.provideShared`(base, layers @ _*) =>
tryBuildProvideZIO2(base, layers).fold(visitIssue(holder, expr), identity)
case fullyApplied @ ScMethodCall(partiallyApplied @ `.provideSomeShared`(base, _ @_*), layers) =>
visitProvideSomeSharedZIO2(partiallyApplied, base, layers).fold(visitIssue(holder, fullyApplied), identity)
tryBuildProvideSomeSharedZIO2(partiallyApplied, base, layers).fold(visitIssue(holder, fullyApplied), identity)
case fullyApplied @ ScMethodCall(partiallyApplied @ ScGenericCall(`ZLayer.makeLike`(_, _), _), layers) =>
tryBuildProvideZIO2(partiallyApplied, layers).fold(visitIssue(holder, fullyApplied), identity)
case fullyApplied @ ScMethodCall(partiallyApplied @ ScGenericCall(`ZLayer.makeSomeLike`(_, _), _), layers) =>
tryBuildProvideSomeZIO2(fullyApplied, partiallyApplied, layers).fold(visitIssue(holder, fullyApplied), identity)
case _ =>
}

Expand All @@ -73,6 +82,14 @@ class ProvideMacroInspection extends LocalInspectionTool {
providedLayers = layers,
method = ProvideMethod.Provide
)
case Typeable(`ZLayerMake[R]`(r)) =>
LayerBuilder
.tryBuildZIO1(base)(
target = split(r),
remainder = Nil,
providedLayers = layers,
method = ProvideMethod.Provide
)
case _ =>
Right(())
}
Expand All @@ -95,11 +112,19 @@ class ProvideMacroInspection extends LocalInspectionTool {
providedLayers = layers,
method = ProvideMethod.Provide
)
case Typeable(`ZLayerMake[R]`(r)) =>
LayerBuilder
.tryBuildZIO2(base)(
target = split(r),
remainder = Nil,
providedLayers = layers,
method = ProvideMethod.Provide
)
case _ =>
Right(())
}

private def visitProvideSomeZIO1(
private def tryBuildProvideSomeZIO1(
expr: ScExpression, // effectLike.injectSome[Foo]
base: ScExpression, // effectLike
layers: Seq[ScExpression] // layer1, layer2
Expand All @@ -121,11 +146,19 @@ class ProvideMacroInspection extends LocalInspectionTool {
providedLayers = layers,
method = ProvideMethod.ProvideSome
)
case Typeable(`ZLayerMakeSome[R0, R]`(r0, r)) =>
LayerBuilder
.tryBuildZIO1(expr)(
target = split(r),
remainder = split(r0),
providedLayers = layers,
method = ProvideMethod.ProvideSome
)
case _ =>
Right(())
}

private def visitProvideSomeZIO2(
private def tryBuildProvideSomeZIO2(
expr: ScExpression, // effectLike.provideSome[Foo]
base: ScExpression, // effectLike
layers: Seq[ScExpression] // layer1, layer2
Expand All @@ -147,11 +180,19 @@ class ProvideMacroInspection extends LocalInspectionTool {
providedLayers = layers,
method = ProvideMethod.ProvideSome
)
case Typeable(`ZLayerMakeSome[R0, R]`(r0, r)) =>
LayerBuilder
.tryBuildZIO2(expr)(
target = split(r),
remainder = split(r0),
providedLayers = layers,
method = ProvideMethod.ProvideSome
)
case _ =>
Right(())
}

private def visitProvideSomeSharedZIO1(
private def tryBuildProvideSomeSharedZIO1(
expr: ScExpression, // effectLike.injectSome[Foo]
base: ScExpression, // effectLike
layers: Seq[ScExpression] // layer1, layer2
Expand All @@ -169,7 +210,7 @@ class ProvideMacroInspection extends LocalInspectionTool {
Right(())
}

private def visitProvideSomeSharedZIO2(
private def tryBuildProvideSomeSharedZIO2(
expr: ScExpression, // effectLike.provideSome[Foo]
base: ScExpression, // effectLike
layers: Seq[ScExpression] // layer1, layer2
Expand Down
38 changes: 36 additions & 2 deletions src/main/scala/zio/intellij/inspections/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -379,8 +379,42 @@ package object inspections {
val `ZIO.foreachParN` = new ZIOCurried3StaticMemberReference("foreachParN")
val `ZIO.partitionParN` = new ZIOCurried3StaticMemberReference("partitionParN")

val `ZLayer.fromEffect` = new ZLayerStaticMemberReference("fromEffect")
val `ZLayer.fromEffectMany` = new ZLayerStaticMemberReference("fromEffectMany")
val `ZLayer.fromEffect` = new ZLayerStaticMemberReference("fromEffect")
val `ZLayer.fromEffectMany` = new ZLayerStaticMemberReference("fromEffectMany")
val `ZLayer.make` = new ZLayerStaticMemberReference("make")
val `ZLayer.makeSome` = new ZLayerStaticMemberReference("makeSome")
val `ZLayer.wire` = new ZLayerStaticMemberReference("wire")
val `ZLayer.wireDebug` = new ZLayerStaticMemberReference("wireDebug")
val `ZLayer.wireSome` = new ZLayerStaticMemberReference("wireSome")
val `ZLayer.wireSomeDebug` = new ZLayerStaticMemberReference("wireSomeDebug")
val `ZLayer.fromMagic` = new ZLayerStaticMemberReference("fromMagic")
val `ZLayer.fromMagicDebug` = new ZLayerStaticMemberReference("fromMagicDebug")
val `ZLayer.fromSomeMagic` = new ZLayerStaticMemberReference("fromSomeMagic")
val `ZLayer.fromSomeMagicDebug` = new ZLayerStaticMemberReference("fromSomeMagicDebug")

object `ZLayer.makeLike` {
def unapply(expr: ScExpression): Option[(ZLayerType, ScExpression)] =
expr match {
case `ZLayer.make`(tpe, expr) => Some((tpe, expr))
case `ZLayer.wire`(tpe, expr) => Some((tpe, expr))
case `ZLayer.wireDebug`(tpe, expr) => Some((tpe, expr))
case `ZLayer.fromMagic`(tpe, expr) => Some((tpe, expr))
case `ZLayer.fromMagicDebug`(tpe, expr) => Some((tpe, expr))
case _ => None
}
}

object `ZLayer.makeSomeLike` {
def unapply(expr: ScExpression): Option[(ZLayerType, ScExpression)] =
expr match {
case `ZLayer.makeSome`(tpe, expr) => Some((tpe, expr))
case `ZLayer.wireSome`(tpe, expr) => Some((tpe, expr))
case `ZLayer.wireSomeDebug`(tpe, expr) => Some((tpe, expr))
case `ZLayer.fromSomeMagic`(tpe, expr) => Some((tpe, expr))
case `ZLayer.fromSomeMagicDebug`(tpe, expr) => Some((tpe, expr))
case _ => None
}
}

object unit {

Expand Down
41 changes: 40 additions & 1 deletion src/main/scala/zio/intellij/utils/TypeCheckUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import zio.intellij.utils.types.{ZLayerTypes, ZioTypes}
object TypeCheckUtils {

val zioCompanionSpecificTypes = List("zio.ZIOCompanionVersionSpecific", "zio.ZIOCompanionPlatformSpecific")
val zioLayerCompanionSpecificTypes = List("zio.ZLayerCompanionVersionSpecific")
val zioLayerCompanionSpecificTypes = List("zio.ZLayerCompanionVersionSpecific", "zio.magic.ZLayerCompanionOps")

val zioTypes = ZioTypes.values.map(_.fqName) :+ "zio.ZIOPlatformSpecific" :+ "zio.ZIOVersionSpecific"
val zioLayerTypes = ZLayerTypes.values.map(_.fqName)
Expand Down Expand Up @@ -58,6 +58,21 @@ object TypeCheckUtils {
def fromZioSpec(tpe: ScType): Boolean =
isOfClassFrom(tpe, zioSpecTypes)

sealed trait TypeArgs1Extractor {

protected def fromTarget(tpe: ScType): Boolean

private def unapplyInner(tpe: ScType): Option[ScType] =
extractAllTypeArguments(tpe).flatMap {
case Seq(r) => Some(r)
case _ => None
}

def unapply(tpe: ScType): Option[ScType] =
if (fromTarget(tpe)) unapplyInner(tpe) else None

}

sealed trait TypeArgs2Extractor {

protected def fromTarget(tpe: ScType): Boolean
Expand Down Expand Up @@ -152,6 +167,30 @@ object TypeCheckUtils {
override protected def fromTarget(tpe: ScType): Boolean = fromZioLayer(tpe)
}

object `ZLayerMake[R]` extends TypeArgs1Extractor {
override protected def fromTarget(tpe: ScType): Boolean =
isOfClassFrom(
tpe,
Seq(
"zio.MakePartiallyApplied",
"zio.magic.FromMagicLayerPartiallyApplied",
"zio.magic.FromMagicLayerDebugPartiallyApplied"
)
)
}

object `ZLayerMakeSome[R0, R]` extends TypeArgs2Extractor {
override protected def fromTarget(tpe: ScType): Boolean =
isOfClassFrom(
tpe,
Seq(
"zio.MakeSomePartiallyApplied",
"zio.magic.FromSomeMagicLayerPartiallyApplied",
"zio.magic.FromSomeMagicLayerDebugPartiallyApplied"
)
)
}

object `zio1.Spec[R, E, T]` extends TypeArgs3Extractor {
override protected def fromTarget(tpe: ScType): Boolean = fromZioSpec(tpe)
}
Expand Down
79 changes: 79 additions & 0 deletions src/test/scala/zio/inspections/ProvideMacroInspectionTest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,85 @@ class ProvideSomeMacroZIO2SpecInspectionTest extends ProvideSomeMacroZIO2SpecIns
class ProvideSomeSharedMacroZIO2SpecInspectionTest
extends ProvideSomeMacroZIO2SpecInspectionTestBase("provideSomeShared")

abstract class ProvideMacroZLayerInspectionTestBase extends ZScalaInspectionTest[ProvideMacroInspection] {
override protected def librariesLoaders: Seq[LibraryLoader] =
if (isZIO1) IvyManagedLoader("io.github.kitlangton" %% "zio-magic" % "0.3.12") +: super.librariesLoaders
else super.librariesLoaders

override protected def description = "Please provide layers for the following"
override protected def descriptionMatches(s: String): Boolean = s != null && s.startsWith(description)
}

abstract class ProvideMacroZIO1ZLayerInspectionTestBase(wire: String) extends ProvideMacroZLayerInspectionTestBase {
def testValidSimpleNoHighlighting(): Unit = z {
s"""import zio.magic._
|
|val layer: ULayer[Has[String]] = ???
|${r(s"ZLayer.$wire[Has[String]](layer)")}""".stripMargin
}.assertNotHighlighted()
def testValidSimpleHighlighting(): Unit = z {
s"""import zio.magic._
|
|val layer: ULayer[Has[String]] = ???
|${r(s"ZLayer.$wire[Has[String] with Has[Int]](layer)")}""".stripMargin
}.assertHighlighted()
}
class ProvideMacroZIO1ZLayerInspectionTest extends ProvideMacroZIO1ZLayerInspectionTestBase("wire")
class ProvideDebugMacroZIO1ZLayerInspectionTest extends ProvideMacroZIO1ZLayerInspectionTestBase("wireDebug")
class ProvideMagicMacroZIO1ZLayerInspectionTest extends ProvideMacroZIO1ZLayerInspectionTestBase("fromMagic")
class ProvideMagicDebugMacroZIO1ZLayerInspectionTest extends ProvideMacroZIO1ZLayerInspectionTestBase("fromMagicDebug")

abstract class ProvideSomeMacroZIO1ZLayerInspectionTestBase(wireSome: String)
extends ProvideMacroZLayerInspectionTestBase {
def testValidSimpleNoHighlighting(): Unit = z {
s"""import zio.magic._
|
|val layer: ULayer[Has[String]] = ???
|${r(s"ZLayer.$wireSome[Has[Boolean], Has[String]](layer)")}""".stripMargin
}.assertNotHighlighted()
def testValidSimpleHighlighting(): Unit = z {
s"""import zio.magic._
|
|val layer: ULayer[Has[String]] = ???
|${r(s"ZLayer.$wireSome[Has[Boolean], Has[String] with Has[Int]](layer)")}""".stripMargin
}.assertHighlighted()
}
class ProvideSomeMacroZIO1ZLayerInspectionTest extends ProvideSomeMacroZIO1ZLayerInspectionTestBase("wireSome")
class ProvideSomeDebugMacroZIO1ZLayerInspectionTest
extends ProvideSomeMacroZIO1ZLayerInspectionTestBase("wireSomeDebug")
class ProvideSomeMagicMacroZIO1ZLayerInspectionTest
extends ProvideSomeMacroZIO1ZLayerInspectionTestBase("fromSomeMagic")
class ProvideSomeMagicDebugMacroZIO1ZLayerInspectionTest
extends ProvideSomeMacroZIO1ZLayerInspectionTestBase("fromSomeMagicDebug")

class ProvideMacroZIO2ZLayerInspectionTest extends ProvideMacroZLayerInspectionTestBase {
override protected def isZIO1 = false
def testValidSimpleNoHighlighting(): Unit = z {
s"""
|val layer: ULayer[String] = ???
|${r(s"ZLayer.make[String](layer)")}""".stripMargin
}.assertNotHighlighted()
def testValidSimpleHighlighting(): Unit = z {
s"""
|val layer: ULayer[String] = ???
|${r(s"ZLayer.make[String with Int](layer)")}""".stripMargin
}.assertHighlighted()
}

class ProvideSomeMacroZIO2ZLayerInspectionTest extends ProvideMacroZLayerInspectionTestBase {
override protected def isZIO1 = false
def testValidSimpleNoHighlighting(): Unit = z {
s"""
|val layer: ULayer[String] = ???
|${r(s"ZLayer.makeSome[Boolean, String](layer)")}""".stripMargin
}.assertNotHighlighted()
def testValidSimpleHighlighting(): Unit = z {
s"""
|val layer: ULayer[String] = ???
|${r(s"ZLayer.makeSome[Boolean, String with Int](layer)")}""".stripMargin
}.assertHighlighted()
}

// special test to expect _very_ specific error message
// checking if types are rendered correctly
class ProvideMacroInspectionRenderingTest extends ZScalaInspectionTest[ProvideMacroInspection] {
Expand Down

0 comments on commit 7c48579

Please sign in to comment.