Skip to content

Commit

Permalink
Capture implicit parameters at Generic materialization time
Browse files Browse the repository at this point in the history
Aligns the way implicit parameters work for case classes and
other supported product types.
  • Loading branch information
joroKr21 committed Apr 27, 2024
1 parent fc30658 commit fb20474
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 32 deletions.
46 changes: 21 additions & 25 deletions core/shared/src/main/scala/shapeless/generic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -593,18 +593,23 @@ trait CaseClassMacros extends ReprTypes with CaseClassMacrosVersionSpecifics {
else mkCoproductTypTree1(ctorsOf1(tpe), param, arg)
}

/** Returns the parameter lists of `tpe`, removing any implicit parameters. */
private def nonImplicitParamLists(tpe: Type): List[List[Symbol]] =
tpe.paramLists.takeWhile(params => params.isEmpty || !params.head.isImplicit)

def isCaseClassLike(sym: ClassSymbol): Boolean = {
def isConcrete = !(sym.isAbstract || sym.isTrait || sym == symbolOf[Object])
def isFinalLike = sym.isFinal || sym.knownDirectSubclasses.isEmpty
def ctor = for {
ctor <- accessiblePrimaryCtorOf(sym.typeSignature)
Seq(params) <- Option(ctor.typeSignature.paramLists)
if params.size == fieldsOf(sym.typeSignature).size
} yield ctor
sym.isCaseClass || (isConcrete && isFinalLike && ctor.isDefined)
def constructor = for {
constructor <- accessiblePrimaryCtorOf(sym.typeSignature)
Seq(params) <- Option(nonImplicitParamLists(constructor.typeSignature))
if params.length == fieldsOf(sym.typeSignature).length
} yield constructor
sym.isCaseClass || (isConcrete && isFinalLike && constructor.isDefined)
}

def isCaseObjectLike(sym: ClassSymbol): Boolean = sym.isModuleClass
def isCaseObjectLike(sym: ClassSymbol): Boolean =
sym.isModuleClass

def isCaseAccessorLike(sym: TermSymbol, inCaseClass: Boolean): Boolean = {
val isGetter =
Expand Down Expand Up @@ -777,26 +782,17 @@ trait CaseClassMacros extends ReprTypes with CaseClassMacrosVersionSpecifics {
def numNonCaseParamLists(tpe: Type): Int = {
val companion = patchedCompanionSymbolOf(tpe.typeSymbol).typeSignature
val apply = companion.member(TermName("apply"))
if (apply.isMethod && !isNonGeneric(apply) && isAccessible(companion, apply)) {
val paramLists = apply.typeSignatureIn(companion).paramLists
val numParamLists = paramLists.length
if (numParamLists <= 1) 0
else {
if (paramLists.last.headOption.exists(_.isImplicit))
numParamLists-2
else
numParamLists-1
}
} else 0
if (!apply.isMethod || isNonGeneric(apply) || !isAccessible(companion, apply)) 0
else nonImplicitParamLists(apply.typeSignatureIn(companion)).length.max(1) - 1
}

object HasApply {
def unapply(tpe: Type): Option[List[(TermName, Type)]] = for {
companion <- Option(patchedCompanionSymbolOf(tpe.typeSymbol).typeSignature)
apply = companion.member(TermName("apply"))
apply <- Option(companion.member(TermName("apply")))
if apply.isMethod && !isNonGeneric(apply)
if isAccessible(companion, apply)
Seq(params) <- Option(apply.typeSignatureIn(companion).paramLists)
Seq(params) <- Option(nonImplicitParamLists(apply.typeSignatureIn(companion)))
aligned <- alignFields(tpe, for (param <- params)
yield param.name.toTermName -> param.typeSignature)
} yield aligned
Expand All @@ -805,19 +801,19 @@ trait CaseClassMacros extends ReprTypes with CaseClassMacrosVersionSpecifics {
object HasUnapply {
def unapply(tpe: Type): Option[List[Type]] = for {
companion <- Option(patchedCompanionSymbolOf(tpe.typeSymbol).typeSignature)
unapply = companion.member(TermName("unapply"))
unapply <- Option(companion.member(TermName("unapply")))
if unapply.isMethod && !isNonGeneric(unapply)
if isAccessible(companion, unapply)
returnTpe <- unapply.asMethod.typeSignatureIn(companion).finalResultType
returnTpe <- unapply.typeSignatureIn(companion).finalResultType
.baseType(symbolOf[Option[_]]).typeArgs.headOption
} yield if (returnTpe <:< typeOf[Product]) returnTpe.typeArgs else List(returnTpe)
}

object HasUniqueCtor {
def unapply(tpe: Type): Option[List[(TermName, Type)]] = for {
ctor <- accessiblePrimaryCtorOf(tpe)
if !isNonGeneric(ctor)
Seq(params) <- Option(ctor.typeSignatureIn(tpe).paramLists)
constructor <- accessiblePrimaryCtorOf(tpe)
if !isNonGeneric(constructor)
Seq(params) <- Option(nonImplicitParamLists(constructor.typeSignatureIn(tpe)))
aligned <- alignFields(tpe, for (param <- params)
yield param.name.toTermName -> param.typeSignature)
} yield aligned
Expand Down
21 changes: 14 additions & 7 deletions core/shared/src/test/scala/shapeless/generic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,12 @@ package GenericTestsAux {
}

case class CCOrdered[A: Ordering](value: A)
class CCLikeOrdered[A: Ordering](val value: A)
class CCLikeOrdered[A: Ordering](val value: A) {
override def equals(that: Any): Boolean = that match {
case that: CCLikeOrdered[_] => this.value == that.value
case _ => false
}
}

case class CCDegen(i: Int)()
class CCLikeDegen(val i: Int)()
Expand Down Expand Up @@ -841,13 +846,15 @@ class GenericTests {
@Test
def testGenericImplicitParams: Unit = {
type Repr = Int :: HNil
val gen = Generic[CCOrdered[Int]]
val cc = CCOrdered(42)
val gen1 = Generic[CCOrdered[Int]]
val gen2 = Generic[CCLikeOrdered[Int]]
val cc1 = CCOrdered(42)
val cc2 = new CCLikeOrdered(42)
val rep = 42 :: HNil

assertTypedEquals[CCOrdered[Int]](gen.from(rep), cc)
assertTypedEquals[Repr](gen.to(cc), rep)
illTyped("Generic[CCLikeOrdered[Int]]")
assertTypedEquals[CCOrdered[Int]](gen1.from(rep), cc1)
assertTypedEquals[CCLikeOrdered[Int]](gen2.from(rep), cc2)
assertTypedEquals[Repr](gen1.to(cc1), rep)
assertTypedEquals[Repr](gen2.to(cc2), rep)
}

@Test
Expand Down

0 comments on commit fb20474

Please sign in to comment.