Skip to content

Commit a0e2935

Browse files
Show message when using unsupported by-name parameters (zio#8117)
* Show message when using unsupported by-name parameters * Formatting * Code review fixes * Make error message much nicer (hopefully)
1 parent e194f98 commit a0e2935

File tree

3 files changed

+112
-3
lines changed

3 files changed

+112
-3
lines changed

core-tests/shared/src/test/scala/zio/autowire/AutoWireSpec.scala

+36-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package zio.autowire
22

33
import zio._
44
import zio.internal.macros.StringUtils.StringOps
5-
import zio.test.Assertion.{equalTo, isLeft}
5+
import zio.test.Assertion.{anything, equalTo, isLeft}
66
import zio.test._
77

88
object AutoWireSpec extends ZIOBaseSpec {
@@ -147,6 +147,41 @@ object AutoWireSpec extends ZIOBaseSpec {
147147
containsStringWithoutAnsi("Double")
148148
)
149149
)
150+
} @@ TestAspect.exceptScala3,
151+
test("works corrently with function parameters") {
152+
case class MyLayer()
153+
154+
def createLayerByFunction(x: () => MyLayer) = ZLayer.succeed(x())
155+
156+
val byName =
157+
ZIO
158+
.service[MyLayer]
159+
.provide(
160+
createLayerByFunction { () =>
161+
val byNameLayer = MyLayer()
162+
byNameLayer
163+
}
164+
)
165+
assertTrue(byName != null)
166+
},
167+
test("return error when passing by-name on Scala 2 (https://github.com/zio/zio/issues/7732)") {
168+
assertZIO(typeCheck {
169+
"""
170+
case class MyLayer()
171+
172+
def createLayerByName(x: => MyLayer) = ZLayer.succeed(x)
173+
174+
val byName =
175+
ZIO
176+
.service[MyLayer]
177+
.provide(
178+
createLayerByName {
179+
val byNameLayer = MyLayer()
180+
byNameLayer
181+
}
182+
)
183+
"""
184+
})(isLeft(anything))
150185
} @@ TestAspect.exceptScala3
151186
),
152187
suite("`ZLayer.makeSome`")(

core/shared/src/main/scala-2/zio/internal/macros/LayerMacroUtils.scala

+29
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package zio.internal.macros
22

33
import zio._
4+
import zio.internal.TerminalRendering
5+
46
import scala.reflect.macros.blackbox
57

68
private[zio] trait LayerMacroUtils {
@@ -9,11 +11,38 @@ private[zio] trait LayerMacroUtils {
911

1012
type LayerExpr = c.Expr[ZLayer[_, _, _]]
1113

14+
private def verifyLayers(layers: Seq[c.Expr[ZLayer[_, _, _]]]): Unit =
15+
for (layer <- layers) {
16+
layer.tree match {
17+
case Apply(tree, _) =>
18+
tree.tpe match {
19+
case MethodType(params, _) =>
20+
val methodName = tree.toString()
21+
val fullMethodSignature = s"def $methodName${tree.tpe}"
22+
23+
val byNameParameters =
24+
params.filter(s => isByName(s.typeSignature)).map(s => s"${s.name}: ${s.typeSignature}")
25+
26+
if (byNameParameters.nonEmpty) {
27+
c.abort(
28+
NoPosition,
29+
TerminalRendering.byNameParameterInMacroError(methodName, fullMethodSignature, byNameParameters)
30+
)
31+
}
32+
}
33+
case _ => ()
34+
}
35+
}
36+
37+
private def isByName(tpe: c.Type): Boolean =
38+
tpe.typeSymbol.isClass && tpe.typeSymbol.asClass == c.universe.definitions.ByNameParamClass
39+
1240
def constructLayer[R0: c.WeakTypeTag, R: c.WeakTypeTag, E](
1341
layers: Seq[c.Expr[ZLayer[_, E, _]]],
1442
provideMethod: ProvideMethod
1543
): c.Expr[ZLayer[R0, E, R]] = {
1644

45+
verifyLayers(layers)
1746
val remainderTypes = getRequirements[R0]
1847
val targetTypes = getRequirements[R]
1948

internal-macros/shared/src/main/scala/zio/internal/TerminalRendering.scala

+47-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package zio.internal
22

3-
import ansi._
3+
import zio.internal.ansi._
44

55
object TerminalRendering {
6-
76
def circularityError(circularDependencies: List[(String, String)]): String = {
87
val circularDependencyMessage =
98
circularDependencies.map { case (a0, b0) =>
@@ -210,6 +209,45 @@ object TerminalRendering {
210209
|""".stripMargin
211210
}
212211

212+
private def colorMethodSignature(fullMethodSignature: String, parametersToColor: Seq[String], color: Color): String =
213+
parametersToColor.fold(fullMethodSignature) { (signature, parameter) =>
214+
signature.replace(parameter, parameter.withAnsi(color))
215+
}
216+
217+
private def replaceByNameWithLambdasAndColorMethodSignature(
218+
fullMethodSignature: String,
219+
parametersToColor: Seq[String],
220+
color: Color
221+
): String =
222+
parametersToColor.fold(fullMethodSignature) { (signature, parameter) =>
223+
signature.replace(parameter, parameter.replace("=>", "() =>").withAnsi(color))
224+
}
225+
226+
def byNameParameterInMacroError(method: String, fullMethodSignature: String, byNameParameters: Seq[String]): String =
227+
s"""
228+
|${title("ZLayer Error").red}
229+
|
230+
| Due to bug in Scala 2 compiler invoking methods with by-name parameters in ${methodName(
231+
"provide"
232+
)}/${methodName("provideSome")} method does not work
233+
| ${colorMethodSignature(fullMethodSignature, byNameParameters, Color.Red)}
234+
|
235+
| Bug can be workarounded in following ways:
236+
|
237+
| 1. Assign method output to temporary variable
238+
| ${s"ZLayer.provide($method(...))".red}
239+
| ↓↓↓
240+
| ${s"val temp = $method(...)".green}
241+
| ${s"ZLayer.provide(temp)".green}
242+
|
243+
| 2. Replace by-name parameter with lambda:
244+
| ${colorMethodSignature(fullMethodSignature, byNameParameters, Color.Red)}
245+
| ↓↓↓
246+
| ${replaceByNameWithLambdasAndColorMethodSignature(fullMethodSignature, byNameParameters, Color.Green)}
247+
|
248+
|${line.red}
249+
|""".stripMargin
250+
213251
def provideSomeNothingEnvError: String =
214252
s"""${title("ZLayer Warning").yellow}
215253

@@ -248,6 +286,13 @@ ${line.yellow}
248286
)
249287
)
250288
)
289+
println(
290+
byNameParameterInMacroError(
291+
"createLayerByName",
292+
"def createLayerByName(i: Int, x: => MyLayer): zio.ULayer[MyLayer]",
293+
Seq("x: => MyLayer")
294+
)
295+
)
251296
}
252297

253298
/**

0 commit comments

Comments
 (0)