From a505dd4cc5e72fc150a02da31773162eab64585e Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Mon, 12 Sep 2022 22:56:21 +0200 Subject: [PATCH] Capture implicit parameters at Generic materialization time Aligns the way implicit parameters work for case classes and other supported product types. --- core/src/main/scala/shapeless/generic.scala | 46 ++++++++++----------- core/src/test/scala/shapeless/generic.scala | 21 ++++++---- 2 files changed, 35 insertions(+), 32 deletions(-) diff --git a/core/src/main/scala/shapeless/generic.scala b/core/src/main/scala/shapeless/generic.scala index e7aa27ddc..cb3432c0e 100644 --- a/core/src/main/scala/shapeless/generic.scala +++ b/core/src/main/scala/shapeless/generic.scala @@ -673,18 +673,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): Boolean = { val isGetter = @@ -867,27 +872,18 @@ 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.map(_.isImplicit).getOrElse(false)) - 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.isTerm && !apply.asTerm.isOverloaded 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 @@ -896,20 +892,20 @@ 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.isTerm && !unapply.asTerm.isOverloaded 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 diff --git a/core/src/test/scala/shapeless/generic.scala b/core/src/test/scala/shapeless/generic.scala index 9b7c97d45..a5cf69928 100644 --- a/core/src/test/scala/shapeless/generic.scala +++ b/core/src/test/scala/shapeless/generic.scala @@ -138,7 +138,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)() @@ -817,13 +822,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