Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for ZLayer provide macro #459

Merged
merged 1 commit into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading