softwaremill/quicklens

Compilation failure when using closure

bbdimitriu opened this issue · 11 comments

Using scala 2.12.4, JDK 8u141, Quicklens 1.4.11.
Using the following setup with an example from the documentation:

case class Street(name: String)
case class Address(street: Option[Street])
case class Person(addresses: List[Address])

val person = Person(List(
            Address(Some(Street("1 Functional Rd."))),
            Address(Some(Street("2 Imperative Dr.")))
))

Then this will work:

person
     .modify(_.addresses.each.street.eachWhere(_.name.startsWith("1")).name)
     .using(_.toUpperCase)

but this will fail with a compilation error:

val one = "1"
person
     .modify(_.addresses.each.street.eachWhere(_.name.startsWith(one)).name)
     .using(_.toUpperCase)

The exception is quite long, but it starts like this:

[error] java.lang.IllegalArgumentException: Could not find proxy for val one: String in List(value one, method $anonfun$new$1, value <local QuickLensIncidentTest>, class QuickLensIncidentTest, package <empty>, package <root>) (currentOwner= method $anonfun$new$9 )
[error] 	at scala.tools.nsc.transform.LambdaLift$LambdaLifter.searchIn$1(LambdaLift.scala:310)
[error] 	at scala.tools.nsc.transform.LambdaLift$LambdaLifter.$anonfun$proxy$4(LambdaLift.scala:315)
[error] 	at scala.tools.nsc.transform.LambdaLift$LambdaLifter.searchIn$1(LambdaLift.scala:315)
[error] 	at scala.tools.nsc.transform.LambdaLift$LambdaLifter.$anonfun$proxy$4(LambdaLift.scala:315)
[error] 	at scala.tools.nsc.transform.LambdaLift$LambdaLifter.searchIn$1(LambdaLift.scala:315)
[error] 	at scala.tools.nsc.transform.LambdaLift$LambdaLifter.$anonfun$proxy$4(LambdaLift.scala:315)
[error] 	at scala.tools.nsc.transform.LambdaLift$LambdaLifter.searchIn$1(LambdaLift.scala:315)
[error] 	at scala.tools.nsc.transform.LambdaLift$LambdaLifter.$anonfun$proxy$4(LambdaLift.scala:315)

So it looks like closures don't work.

Hi,

Same problem, but with a NoSuchElementException instead of a IllegalArgumentException.

The following code is ok:

case class B(n: Int)

val xs = Seq(B(1), B(2), B(3), B(4))
val ys = xs.modify(_.eachWhere(_.n % 2 == 0).n).using(_ * 2)

But this one is not:

case class B(n: Int)

val xs = Seq(B(1), B(2), B(3), B(4))
val test = 0
val ys = xs.modify(_.eachWhere(_.n % 2 == test).n).using(_ * 2)

The error is:

java.util.NoSuchElementException: value test
	at scala.collection.mutable.AnyRefMap$ExceptionDefault.apply(AnyRefMap.scala:425)
	at scala.collection.mutable.AnyRefMap$ExceptionDefault.apply(AnyRefMap.scala:424)
	at scala.collection.mutable.AnyRefMap.apply(AnyRefMap.scala:180)
	at scala.tools.nsc.backend.jvm.BCodeSkelBuilder$PlainSkelBuilder$locals$.load(BCodeSkelBuilder.scala:389)
	at scala.tools.nsc.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoad(BCodeBodyBuilder.scala:354)
	at scala.tools.nsc.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.$anonfun$genLoadArguments$1(BCodeBodyBuilder.scala:935)
	at scala.tools.nsc.backend.jvm.BCodeBodyBuilder$PlainBodyBuilder.genLoadArguments(BCodeBodyBuilder.scala:935)

PS: scala 2.12.4 and quicklens 1.4.11.

As a workaround, I found out that you can rewrite

val one = "1"
person
     .modify(_.addresses.each.street.eachWhere(_.name.startsWith(one)).name)
     .using(_.toUpperCase)

to

val one = "1"
val w: Street => Boolean = _.name.startsWith(one)   // also works when 'one' comes from elsewhere
person
     .modify(_.addresses.each.street.eachWhere(w).name)
     .using(_.toUpperCase)

Not as nice, but at least it compiles.

i ran into the same eachWhere issue with quicklens 1.4.11 and scala 2.12.7.

event match {
  case Bar(fooId, status) =>
    state.modify(_.bars.eachWhere(_.id == fooId).status).setTo(status)
}

however, i wasn't getting any exception; sbt simply failed with the cryptic:

[error] Error while emitting Foo.scala
[error] value fooId

There is a workaround (see @erikvanoosten post):

event match {
  case Bar(fooId, status) =>
    val condition: Bar => Boolean = _.id == fooId
    state.modify(_.bars.eachWhere(condition).status).setTo(status)
}

I wish if the original eachWhere worked.

we stumbled across this bug as well after switching to 2.12. took ages to understand this was due to this very bug, but better late than never. the workaround does work. please let me know if I could help, I am willing to spend time on this fixing it. But would need at least some guidance

adamw commented

@pete-proton I wish I could help you, but I'm not sure where to start as well :) Did you try on 2.13 - is the problem there as well? If not, this might indicate that this is some 2.12-compiler-specific hmm ... let's say "feature", that might be hard to overcome. If this happens also on 2.13, maybe we incorrectly use the tree api in the macro

@adamw I see, I will try with 2.13 and see how that goes. thanks for the quick response

Hi there. Is there any idea about when it would be available?
@adamw I'm using the 2.13 version and I have the same problem.

@pete-proton I wish I could help you, but I'm not sure where to start as well :) Did you try on 2.13 - is the problem there as well? If not, this might indicate that this is some 2.12-compiler-specific hmm ... let's say "feature", that might be hard to overcome. If this happens also on 2.13, maybe we incorrectly use the tree api in the macro

@adamw thank you for the guidance. the issue seems to be reproducible using scala 2.13, see. If you could give directions on how to fix it, I would be very grateful. I just have not worked with macros before. thank you in advance.

adamw commented

@pete-proton If I only knew I would fix it :) I would start by looking at usages of FunctorPathElement in QuicklensMacros, but not sure if this will lead you anywhere ...

thank you, @adamw