From 9ccfa5ce0bd02c66571c10a87c0ceab1096878af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20=C5=A0pan=C4=9Bl?= Date: Sun, 1 Dec 2024 17:20:48 +0100 Subject: [PATCH] Add test for #258, improve error message --- .../softwaremill/quicklens/QuicklensMacros.scala | 10 ++++++++-- .../quicklens/test/ExplicitCopyTest.scala | 13 +++++++++++++ 2 files changed, 21 insertions(+), 2 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 20e640e..b79462a 100644 --- a/quicklens/src/main/scala-3/com/softwaremill/quicklens/QuicklensMacros.scala +++ b/quicklens/src/main/scala-3/com/softwaremill/quicklens/QuicklensMacros.scala @@ -58,6 +58,9 @@ object QuicklensMacros { def noSuchMember(tpeStr: String, name: String) = s"$tpeStr has no member named $name" + def noSuitableMember(tpeStr: String, name: String, argNames: Iterable[String]) = + s"$tpeStr has no member $name with parameters ${argNames.mkString("(", ", ", ")")}" + def multipleMatchingMethods(tpeStr: String, name: String, syms: Seq[Symbol]) = val symsStr = syms.map(s => s" - $s: ${s.termRef.dealias.widen.show}").mkString("\n", "\n", "") s"Multiple methods named $name found in $tpeStr: $symsStr" @@ -181,13 +184,16 @@ object QuicklensMacros { def methodSymbolByNameAndArgsOrError(sym: Symbol, name: String, argsMap: Map[String, Term]): Symbol = { val argNames = argsMap.keys - sym.methodMember(name).filter{ msym => + val allCopyMembers = sym.methodMember(name) + allCopyMembers.filter{ msym => // for copy, we filter out the methods that don't have the desired parameter names val paramNames = msym.paramSymss.flatten.filter(_.isTerm).map(_.name) argNames.forall(paramNames.contains) } match case List(m) => m - case Nil => report.errorAndAbort(noSuchMember(sym.name, name)) + case Nil => + if allCopyMembers.isEmpty then report.errorAndAbort(noSuchMember(sym.name, name)) + else report.errorAndAbort(noSuitableMember(sym.name, name, argNames)) case lst @ (m :: _) => // if we have multiple matching copy methods, pick the synthetic one, if it exists, otherwise, pick any method val syntheticCopies = lst.filter(_.flags.is(Flags.Synthetic)) diff --git a/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/ExplicitCopyTest.scala b/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/ExplicitCopyTest.scala index f1ecce1..46ba044 100644 --- a/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/ExplicitCopyTest.scala +++ b/quicklens/src/test/scala-3/com/softwaremill/quicklens/test/ExplicitCopyTest.scala @@ -77,6 +77,19 @@ class ExplicitCopyTest extends AnyFlatSpec with Matchers { accessed shouldEqual 0 } + it should "not compile when modifying a field which is not present as a copy parameter" in { + """ + case class Content(x: String) + + class A(val c: Content) { + def copy(x: String = c.x): A = new A(Content(x)) + } + + val a = new A(Content("A")) + val am = a.modify(_.c).setTo(Content("B")) + """ shouldNot compile + } + // TODO: Would be nice to be able to handle this case. Based on the types, it // is obvious, that the explicit copy should be picked, but I'm not sure if we // can get that information