Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

When case class has custom unapply, reference getters instead #1256

Merged
merged 1 commit into from
Mar 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions core/src/main/scala/shapeless/generic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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))
Expand Down
26 changes: 25 additions & 1 deletion core/src/test/scala_2.13+/shapeless/GenericTests213.scala
Original file line number Diff line number Diff line change
@@ -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)
Expand All @@ -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)
}
}