From 806184ae9c9136725e90bc2c8ac35f5ad4bebf7d Mon Sep 17 00:00:00 2001 From: Nikita Miazin Date: Thu, 30 Nov 2023 22:19:31 +0200 Subject: [PATCH] add support for ZLayer provide macro --- .../macros/ProvideMacroInspection.scala | 57 +++++++++++-- .../zio/intellij/inspections/package.scala | 38 ++++++++- .../zio/intellij/utils/TypeCheckUtils.scala | 41 +++++++++- .../ProvideMacroInspectionTest.scala | 79 +++++++++++++++++++ 4 files changed, 204 insertions(+), 11 deletions(-) diff --git a/src/main/scala/zio/intellij/inspections/macros/ProvideMacroInspection.scala b/src/main/scala/zio/intellij/inspections/macros/ProvideMacroInspection.scala index a2ab94cd..32367371 100644 --- a/src/main/scala/zio/intellij/inspections/macros/ProvideMacroInspection.scala +++ b/src/main/scala/zio/intellij/inspections/macros/ProvideMacroInspection.scala @@ -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._ @@ -35,11 +36,15 @@ 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 _ => } @@ -47,11 +52,15 @@ class ProvideMacroInspection extends LocalInspectionTool { 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 _ => } @@ -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(()) } @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/src/main/scala/zio/intellij/inspections/package.scala b/src/main/scala/zio/intellij/inspections/package.scala index 4ecedee2..9730e384 100644 --- a/src/main/scala/zio/intellij/inspections/package.scala +++ b/src/main/scala/zio/intellij/inspections/package.scala @@ -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 { diff --git a/src/main/scala/zio/intellij/utils/TypeCheckUtils.scala b/src/main/scala/zio/intellij/utils/TypeCheckUtils.scala index 69a788cd..d787aaef 100644 --- a/src/main/scala/zio/intellij/utils/TypeCheckUtils.scala +++ b/src/main/scala/zio/intellij/utils/TypeCheckUtils.scala @@ -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) @@ -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 @@ -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) } diff --git a/src/test/scala/zio/inspections/ProvideMacroInspectionTest.scala b/src/test/scala/zio/inspections/ProvideMacroInspectionTest.scala index 1d9f788e..f61ffcf6 100644 --- a/src/test/scala/zio/inspections/ProvideMacroInspectionTest.scala +++ b/src/test/scala/zio/inspections/ProvideMacroInspectionTest.scala @@ -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] {