From 5ca40ddc242c6d4ccfa2020dabb21fa5b07dbfa5 Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Fri, 25 Mar 2022 23:58:27 +0100 Subject: [PATCH] When case class has custom unapply, reference getters instead --- core/src/main/scala/shapeless/generic.scala | 21 ++++++++++----- .../shapeless/GenericTests213.scala | 26 ++++++++++++++++++- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/core/src/main/scala/shapeless/generic.scala b/core/src/main/scala/shapeless/generic.scala index 20cda2a8a..6f4368029 100644 --- a/core/src/main/scala/shapeless/generic.scala +++ b/core/src/main/scala/shapeless/generic.scala @@ -368,10 +368,14 @@ trait CaseClassMacros extends ReprTypes with CaseClassMacrosVersionSpecifics { * */ def fieldsOf(tpe: Type): List[(TermName, Type)] = { val clazz = tpe.typeSymbol.asClass + // Case class field names have an extra space at the end. + val nameOf: TermSymbol => TermName = + if (!clazz.isCaseClass) _.name + else field => TermName(field.name.toString.dropRight(1)) if (isCaseObjectLike(clazz) || isAnonOrRefinement(clazz)) Nil else tpe.decls.sorted.collect { - case sym: TermSymbol if isCaseAccessorLike(sym) => - (sym.name, sym.typeSignatureIn(tpe).finalResultType) + case field: TermSymbol if isCaseAccessorLike(field) => + nameOf(field) -> field.typeSignatureIn(tpe).finalResultType } } @@ -496,11 +500,14 @@ trait CaseClassMacros extends ReprTypes with CaseClassMacrosVersionSpecifics { } } - def nameAsString(name: Name): String = name.decodedName.toString.trim + def nameAsString(name: Name): String = + name.decodedName.toString - def nameAsValue(name: Name): Constant = Constant(nameAsString(name)) + def nameAsValue(name: Name): Constant = + Constant(nameAsString(name)) - def nameOf(tpe: Type) = tpe.typeSymbol.name + def nameOf(tpe: Type): Name = + tpe.typeSymbol.name def mkHListValue(elems: List[Tree]): Tree = { val cons = objectRef[::.type] @@ -1030,8 +1037,10 @@ trait CaseClassMacros extends ReprTypes with CaseClassMacrosVersionSpecifics { (const(singleton), const(objectRef[HNil.type])) // case 3: case class case tpe if tpe.typeSymbol.asClass.isCaseClass => + val companion = patchedCompanionSymbolOf(tpe.typeSymbol) + val unapply = companion.typeSignature.member(TermName("unapply")) val fields = fieldsOf(tpe) - (fromApply(fields), toUnapply(fields)) + (fromApply(fields), if (unapply.isSynthetic) toUnapply(fields) else toGetters(fields)) // case 4: exactly one matching public apply/unapply case HasApplyUnapply(args) => (fromApply(args), toUnapply(args)) diff --git a/core/src/test/scala_2.13+/shapeless/GenericTests213.scala b/core/src/test/scala_2.13+/shapeless/GenericTests213.scala index 420f3aee9..057f20e4b 100644 --- a/core/src/test/scala_2.13+/shapeless/GenericTests213.scala +++ b/core/src/test/scala_2.13+/shapeless/GenericTests213.scala @@ -1,6 +1,9 @@ package shapeless -import shapeless.test.illTyped +import org.junit.Assert.assertEquals +import org.junit.Test +import shapeless.test.{illTyped, typed} +import shapeless.testutil.assertTypedEquals object GenericTests213 { case class WrongApplySignature private(value: String) @@ -9,5 +12,26 @@ object GenericTests213 { def apply(v: String): Either[String, WrongApplySignature] = Left("No ways") } + case class CCWithCustomUnapply(x: Int, y: String) + object CCWithCustomUnapply { + def unapply(cc: CCWithCustomUnapply): Option[(Int, String, String)] = None + } +} + +class GenericTests213 { + import GenericTests213._ + illTyped("Generic[WrongApplySignature]") + + @Test + def testCCWithCustomUnapply(): Unit = { + val cc = CCWithCustomUnapply(23, "foo") + val gen = Generic[CCWithCustomUnapply] + val r = gen.to(cc) + val f = gen.from(13 :: "bar" :: HNil) + assertTypedEquals[Int :: String :: HNil](23 :: "foo" :: HNil, r) + typed[CCWithCustomUnapply](f) + assertEquals(13, f.x) + assertEquals("bar", f.y) + } }