diff --git a/quicklens/src/main/scala-3/com/softwaremill/quicklens/package.scala b/quicklens/src/main/scala-3/com/softwaremill/quicklens/package.scala index f544c3b..12e3e9d 100644 --- a/quicklens/src/main/scala-3/com/softwaremill/quicklens/package.scala +++ b/quicklens/src/main/scala-3/com/softwaremill/quicklens/package.scala @@ -7,7 +7,9 @@ import scala.reflect.ClassTag package object quicklens { - extension [S, A](inline obj: S) + // #114: obj shouldn't be inline since we want to reference the parameter by-name, rather then embedding the whole + // expression whenever obj is used; this is especially important for chained .modify invocations + extension [S, A](obj: S) /** Create an object allowing modifying the given (deeply nested) field accessible in a `case class` hierarchy via * `path` on the given `obj`. * diff --git a/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/CompileTimeTest.scala b/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/CompileTimeTest.scala new file mode 100644 index 0000000..54b898a --- /dev/null +++ b/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/CompileTimeTest.scala @@ -0,0 +1,27 @@ +package com.softwaremill.quicklens.test + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class CompileTimeTest extends AnyFlatSpec with Matchers { + // #114 + it should "not compile for too long in case of chained modify invocations" in { + val start = System.currentTimeMillis() + assertDoesNotCompile(""" + case class B(a1: Double, a2: Double, a3: Double, a4: Double, a5: Double) + case class C(b: B) + + import com.softwaremill.quicklens.* + + val c = C(B(1, 1, 1, 1, 1)) + c + .modify(_.b.a1).setTo("") + .modify(_.b.a2).setTo("") + .modify(_.b.a3).setTo("") + .modify(_.b.a4).setTo("") + .modify(_.b.a5).setTo("") + """) + val end = System.currentTimeMillis() + (end - start) shouldBe <=(5000L) // that's a lot anyway + } +} diff --git a/quicklens/src/test/scala/com/softwaremill/quicklens/RepeatedModifyTest.scala b/quicklens/src/test/scala/com/softwaremill/quicklens/RepeatedModifyTest.scala new file mode 100644 index 0000000..b3d7df7 --- /dev/null +++ b/quicklens/src/test/scala/com/softwaremill/quicklens/RepeatedModifyTest.scala @@ -0,0 +1,43 @@ +package com.softwaremill.quicklens + +import org.scalatest.flatspec.AnyFlatSpec +import org.scalatest.matchers.should.Matchers + +class RepeatedModifyTest extends AnyFlatSpec with Matchers { + import RepeatedModifyTest._ + + it should "properly handle repeated modify invocations for different fields" in { + val c = C(B(1, 1, 1, 1, 1)) + c + .modify(_.b.a1) + .setTo(0.0d) + .modify(_.b.a2) + .setTo(0.0d) + .modify(_.b.a3) + .setTo(0.0d) + .modify(_.b.a4) + .setTo(0.0d) + .modify(_.b.a5) + .setTo(0.0d) shouldBe C(B(0, 0, 0, 0, 0)) + } + + it should "properly handle repeated modify invocations for the same field" in { + val c = C(B(1, 1, 1, 1, 1)) + c + .modify(_.b.a1) + .setTo(0.0d) + .modify(_.b.a1) + .setTo(1.0d) + .modify(_.b.a1) + .setTo(2.0d) + .modify(_.b.a1) + .setTo(3.0d) + .modify(_.b.a1) + .setTo(4.0d) shouldBe C(B(4, 1, 1, 1, 1)) + } +} + +object RepeatedModifyTest { + case class B(a1: Double, a2: Double, a3: Double, a4: Double, a5: Double) + case class C(b: B) +}