From 25d8c0e2c0c54fb575bc53e2b029bf188610eb71 Mon Sep 17 00:00:00 2001 From: Kacper Korban Date: Tue, 17 Sep 2024 14:17:38 +0200 Subject: [PATCH] When accessing current values of fields, try the field symbol first before the method symbol --- .../softwaremill/quicklens/QuicklensMacros.scala | 15 ++++++++------- .../quicklens/test/ExplicitCopyTest.scala | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 7 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 54684ee..d352f21 100644 --- a/quicklens/src/main/scala-3/com/softwaremill/quicklens/QuicklensMacros.scala +++ b/quicklens/src/main/scala-3/com/softwaremill/quicklens/QuicklensMacros.scala @@ -168,13 +168,13 @@ object QuicklensMacros { def symbolAccessorByNameOrError(sym: Symbol, name: String): Symbol = { val mem = sym.fieldMember(name) if mem != Symbol.noSymbol then mem - else report.errorAndAbort(noSuchMember(sym.name, name)) + else symbolMethodByNameOrError(sym, name) } def symbolMethodByNameOrError(sym: Symbol, name: String): Symbol = { sym.methodMember(name) match case List(m) => m - case Nil => symbolAccessorByNameOrError(sym, name) + case Nil => report.errorAndAbort(noSuchMember(sym.name, name)) case _ => report.errorAndAbort(multipleMatchingMethods(sym.name, name)) } @@ -202,9 +202,10 @@ object QuicklensMacros { obj: Term, fields: Seq[(PathSymbol.Field, Seq[PathTree])] ): Term = { - val objSymbol = obj.tpe.widenAll.matchingTypeSymbol + val objTpe = obj.tpe.widenAll + val objSymbol = objTpe.matchingTypeSymbol if isSum(objSymbol) then { - obj.tpe.widenAll match { + objTpe match { case AndType(_, _) => report.errorAndAbort( s"Implementation limitation: Cannot modify sealed hierarchies mixed with & types. Try providing a more specific type." @@ -235,7 +236,7 @@ object QuicklensMacros { } else if isProduct(objSymbol) || isProductLike(objSymbol) then { val copy = symbolMethodByNameOrError(objSymbol, "copy") val argsMap: Map[String, Term] = fields.map { (field, trees) => - val fieldMethod = symbolMethodByNameOrError(objSymbol, field.name) + val fieldMethod = symbolAccessorByNameOrError(objSymbol, field.name) val resTerm: Term = trees.foldLeft[Term](Select(obj, fieldMethod)) { (term, tree) => mapToCopy(owner, mod, term, tree) } @@ -243,7 +244,7 @@ object QuicklensMacros { field.name -> namedArg }.toMap - val typeParams = obj.tpe.widenAll match { + val typeParams = objTpe match { case AppliedType(_, typeParams) => Some(typeParams) case _ => None } @@ -272,7 +273,7 @@ object QuicklensMacros { case _ => Apply(Select(obj, copy), args) } } else - report.errorAndAbort(s"Unsupported source object: must be a case class or sealed trait, but got: $objSymbol") + report.errorAndAbort(s"Unsupported source object: must be a case class or sealed trait, but got: $objSymbol of type ${objTpe.show} (${obj.show})") } def applyFunctionDelegate( 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 e2a52ac..444fbf6 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 @@ -22,4 +22,18 @@ class ExplicitCopyTest extends AnyFlatSpec with Matchers { modified.show shouldEqual expected.show } + it should "modify a class that has a method with the same name as a field" in { + final case class PathItem() + final case class Paths( + pathItems: Map[String, PathItem] = Map.empty + ) + final case class Docs( + paths: Paths = Paths() + ) { + def paths(paths: Paths): Docs = copy(paths = paths) + } + val docs = Docs() + docs.modify(_.paths.pathItems).using(m => m + ("a" -> PathItem())) + } + }