Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Forward port changes from series/2.3 #1273

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
76 changes: 31 additions & 45 deletions core/shared/src/main/scala/shapeless/generic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ trait CaseClassMacros extends ReprTypes with CaseClassMacrosVersionSpecifics {

def isProductAux(tpe: Type): Boolean =
tpe.typeSymbol.isClass && {
val cls = classSym(tpe)
val cls = tpe.typeSymbol.asClass
isCaseObjectLike(cls) || isCaseClassLike(cls) || HasApplyUnapply(tpe) || HasCtorUnapply(tpe)
}

Expand All @@ -311,7 +311,7 @@ trait CaseClassMacros extends ReprTypes with CaseClassMacrosVersionSpecifics {

def isCoproduct(tpe: Type): Boolean =
tpe.typeSymbol.isClass && {
val cls = classSym(tpe)
val cls = tpe.typeSymbol.asClass
(cls.isTrait || cls.isAbstract) && cls.isSealed
}

Expand Down Expand Up @@ -368,18 +368,13 @@ trait CaseClassMacros extends ReprTypes with CaseClassMacrosVersionSpecifics {
}

def ctorsOfAux(tpe: Type, hk: Boolean): List[Type] = {
def collectCtors(classSym: ClassSymbol): List[ClassSymbol] = {
classSym.knownDirectSubclasses.toList flatMap { child0 =>
val child = child0.asClass
child.typeSignature // Workaround for <https://issues.scala-lang.org/browse/SI-7755>
if (isCaseClassLike(child) || isCaseObjectLike(child))
List(child)
else if (child.isSealed)
collectCtors(child)
else
abort(s"$child is not case class like or a sealed trait")
def collectCtors(classSym: ClassSymbol): List[ClassSymbol] =
classSym.knownDirectSubclasses.toList.flatMap { child =>
val cls = child.asClass
if (isProductAux(cls.typeSignature)) List(cls)
else if (cls.isSealed) collectCtors(cls)
else abort(s"$cls is not case class like or a sealed trait")
}
}

if(isProduct(tpe))
List(tpe)
Expand Down Expand Up @@ -598,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 All @@ -620,13 +620,8 @@ trait CaseClassMacros extends ReprTypes with CaseClassMacrosVersionSpecifics {

def classSym(tpe: Type): ClassSymbol = {
val sym = tpe.typeSymbol
if (!sym.isClass)
abort(s"$sym is not a class or trait")

val classSym = sym.asClass
classSym.typeSignature // Workaround for <https://issues.scala-lang.org/browse/SI-7755>

classSym
if (!sym.isClass) abort(s"$sym is not a class or trait")
sym.asClass
}

// See https://github.com/milessabin/shapeless/issues/212
Expand Down Expand Up @@ -787,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 @@ -815,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
45 changes: 38 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 All @@ -164,6 +169,20 @@ package GenericTestsAux {
@generateGeneric
object A
}

sealed trait PubOrPriv
final case class Pub(x: Int) extends PubOrPriv
final class Priv private(val y: String) extends PubOrPriv {
override def equals(that: Any): Boolean = that match {
case that: Priv => this.y == that.y
case _ => false
}
}

object Priv {
def apply(y: String): Priv = new Priv(y)
def unapply(p: Priv): Some[String] = Some(p.y)
}
}

class GenericTests {
Expand Down Expand Up @@ -827,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 All @@ -853,6 +874,7 @@ class GenericTests {
illTyped("Generic[Squared]")
}

@Test
def testCoproductWithFreeTypeParams: Unit = {
type Repr[A] = ConstTap[A] :+: InTap[A, _] :+: OutTap[A, _] :+: PipeTap[A, _] :+: CNil
val gen = Generic[Tap[String]]
Expand All @@ -874,6 +896,15 @@ class GenericTests {
assertEquals(gen.to(expected), actual)
}
}

@Test
def testPublicAndPrivateCoproductChildren: Unit = {
val gen = Generic[PubOrPriv]
assertEquals(Pub(123), gen.from(Inr(Inl(Pub(123)))))
assertEquals(Inr(Inl(Pub(123))), gen.to(Pub(123)))
assertEquals(Priv("secret"), gen.from(Inl(Priv("secret"))))
assertEquals(Inl(Priv("secret")), gen.to(Priv("secret")))
}
}

package GenericTestsAux2 {
Expand Down
10 changes: 7 additions & 3 deletions examples/src/main/scala/shapeless/examples/enum.scala
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,12 @@ object ScalaEnumDemo /*extends App*/ {
//
object ShapelessEnumDemo extends App {
// ADT as an enumeration. Barely any more boilerplate ...
sealed trait WeekDay
sealed abstract class WeekDay(val ordinal: Int) extends Serializable
object WeekDay {
val Mon, Tue, Wed, Thu, Fri, Sat, Sun = new WeekDay {}
private var ordinal = 0
val Mon, Tue, Wed, Thu, Fri, Sat, Sun =
try new WeekDay(ordinal) {}
finally ordinal += 1
val values: Set[WeekDay] = Values
}

Expand All @@ -68,7 +71,8 @@ object ShapelessEnumDemo extends App {
case _ => false // compile time non-exhaustive match warning/error without this case
}

assert(!isWeekend(Mon)) //
assert(!isWeekend(Mon))
assert(values.size == values.map(_.ordinal).size)
}

// Infrastructure for the above. Original version due to Travis Brown,
Expand Down
Loading