From b6f89be6402945f61f181668cd7ed86f898a46a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0pan=C4=9Bl?= Date: Fri, 11 Apr 2025 11:23:34 +0200 Subject: [PATCH 1/4] Compile with -Xcheck-macros --- build.sbt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 9490d53..31a3493 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,7 @@ excludeLintKeys in Global ++= Set(ideSkipProject) val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( organization := "com.softwaremill.quicklens", updateDocs := UpdateVersionInDocs(sLog.value, organization.value, version.value, List(file("README.md"))), - scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked"), // useful for debugging macros: "-Ycheck:all", "-Xcheck-macros" + scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xcheck-macros"), ideSkipProject := (scalaVersion.value != scalaIdeaVersion) ) From 44c1062030c31b64762c8a2a0cf081649f3e2d9a Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 14 Apr 2025 08:47:01 +0200 Subject: [PATCH 2/4] Disallow unsound &-type modifications --- .../quicklens/QuicklensMacros.scala | 2 ++ .../quicklens/test/ModifyAndTypeTest.scala | 19 +++---------------- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/quicklens/src/main/scala-3/com/softwaremill/quicklens/QuicklensMacros.scala b/quicklens/src/main/scala-3/com/softwaremill/quicklens/QuicklensMacros.scala index 6215ed3..8d558e8 100644 --- a/quicklens/src/main/scala-3/com/softwaremill/quicklens/QuicklensMacros.scala +++ b/quicklens/src/main/scala-3/com/softwaremill/quicklens/QuicklensMacros.scala @@ -157,6 +157,8 @@ object QuicklensMacros { def poorMansLUB: TypeRepr = tpe match { case AndType(l, r) if l <:< r => l case AndType(l, r) if r <:< l => r + case AndType(_, _) => + report.errorAndAbort(s"Implementation limitation: Cannot modify an & type with unrelated types.") case _ => tpe } diff --git a/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/ModifyAndTypeTest.scala b/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/ModifyAndTypeTest.scala index 5d97a9f..8102b5a 100644 --- a/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/ModifyAndTypeTest.scala +++ b/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/ModifyAndTypeTest.scala @@ -9,6 +9,7 @@ object ModifyAndTypeTest { case class A(a: Int) extends B trait B { def a: Int + def b: Int = 0 } case class A1(a: Int) @@ -26,6 +27,7 @@ class ModifyAndTypeTest extends AnyFlatSpec with Matchers { val modified = ab.modify(_.a).setTo(1) + modified.b shouldBe 0 modified.a shouldBe 1 } @@ -34,22 +36,7 @@ class ModifyAndTypeTest extends AnyFlatSpec with Matchers { val modified = ab.modify(_.a).setTo(1) - modified.a shouldBe 1 - } - - it should "modify an & type object 2" in { - val ab: B & A1 = new A1(0) with B - - val modified = ab.modify(_.a).setTo(1) - - modified.a shouldBe 1 - } - - it should "modify an & type object 3" in { - val ab: A1 & B = new A1(0) with B - - val modified = ab.modify(_.a).setTo(1) - + modified.b shouldBe 0 modified.a shouldBe 1 } From 8c5f74bbab6370f7f31fa71e4b79ae2bcca850f5 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Mon, 14 Apr 2025 09:30:56 +0200 Subject: [PATCH 3/4] Patch: explicitly pass type parameter lists; might not work with clause interleaving --- .../com/softwaremill/quicklens/QuicklensMacros.scala | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/quicklens/src/main/scala-3/com/softwaremill/quicklens/QuicklensMacros.scala b/quicklens/src/main/scala-3/com/softwaremill/quicklens/QuicklensMacros.scala index 8d558e8..21aa38a 100644 --- a/quicklens/src/main/scala-3/com/softwaremill/quicklens/QuicklensMacros.scala +++ b/quicklens/src/main/scala-3/com/softwaremill/quicklens/QuicklensMacros.scala @@ -178,6 +178,10 @@ object QuicklensMacros { extension (term: Term) def appliedToIfNeeded(args: List[Term]): Term = if args.isEmpty then term else term.appliedToArgs(args) + def appliedToTypesIfNeeded(args: List[TypeRepr]): Term = + if term.symbol.paramSymss.headOption.toList.flatten.filter(_.isType).nonEmpty then + term.appliedToTypes(args) + else term def symbolAccessorByNameOrError(obj: Term, name: String): Term = { val objTpe = obj.tpe.widenAll @@ -253,7 +257,7 @@ object QuicklensMacros { val methodSymbol = methodSymbolByNameOrError(objSymbol, copy.name + "$default$" + i.toString) // default values in extension methods take the extension receiver as the first parameter val defaultMethodArgs = argsMap.dropRight(1).flatMap(_.values) - obj.select(methodSymbol).appliedToIfNeeded(defaultMethodArgs) + obj.select(methodSymbol).appliedToTypesIfNeeded(typeParams).appliedToIfNeeded(defaultMethodArgs) n -> v.getOrElse(defaultMethod) }.toMap From 713c139666a3801d9297fd09ada2e989424ae5aa Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Thu, 24 Apr 2025 11:25:23 +0200 Subject: [PATCH 4/4] Only set -Xcheck-macros for scala 3 projects --- build.sbt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/build.sbt b/build.sbt index 31a3493..30e7e9f 100644 --- a/build.sbt +++ b/build.sbt @@ -14,7 +14,14 @@ excludeLintKeys in Global ++= Set(ideSkipProject) val commonSettings = commonSmlBuildSettings ++ ossPublishSettings ++ Seq( organization := "com.softwaremill.quicklens", updateDocs := UpdateVersionInDocs(sLog.value, organization.value, version.value, List(file("README.md"))), - scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked", "-Xcheck-macros"), + scalacOptions ++= Seq("-deprecation", "-feature", "-unchecked"), + scalacOptions ++= { + val sv = (Compile / scalaVersion).value + CrossVersion.partialVersion(sv) match { + case Some((3, _)) => List("-Xcheck-macros") + case _ => Nil + } + }, ideSkipProject := (scalaVersion.value != scalaIdeaVersion) )