From d8fb2f5ca2edc1a34ed7831f06d5eca08ba4989c Mon Sep 17 00:00:00 2001 From: Georgi Krastev Date: Wed, 29 Nov 2017 19:53:01 +0100 Subject: Existentially abstract unbound subtype parameters That happens when the subtype of a sealed trait has more type parameters than its parent. When those extra type parameters are covariant they are replaced by their upper bounds, otherwise they are existentially quantified. --- CONTRIBUTORS | 2 +- core/shared/src/main/scala/magnolia.scala | 16 +++++++++------- examples/shared/src/main/scala/show.scala | 6 ++++++ tests/src/main/scala/tests.scala | 23 +++++++++++++++++++++++ 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/CONTRIBUTORS b/CONTRIBUTORS index f495d8d..e376ca4 100644 --- a/CONTRIBUTORS +++ b/CONTRIBUTORS @@ -1,3 +1,3 @@ Jon Pretty [@propensive](https://twitter.com/propensive/) Loïc Descotte - +Georgi Krastev [@joro_kr](https://twitter.com/joro_kr/) diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index 2113dff..62a36a5 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -65,6 +65,7 @@ object Magnolia { * */ def gen[T: c.WeakTypeTag](c: whitebox.Context): c.Tree = { import c.universe._ + import internal._ val magnoliaPkg = q"_root_.magnolia" val scalaPkg = q"_root_.scala" @@ -246,9 +247,7 @@ object Magnolia { val caseParamsReversed = caseClassParameters.foldLeft[List[CaseParam]](Nil) { (acc, param) => val paramName = param.name.decodedName.toString - val paramType = param.returnType.substituteTypes(genericType.etaExpand.typeParams, - genericType.typeArgs) - + val paramType = param.typeSignatureIn(genericType).resultType val predefinedRef = acc.find(_.paramType == paramType) val caseParamOpt = predefinedRef.map { backRef => @@ -332,10 +331,13 @@ object Magnolia { } else if (isSealedTrait) { val genericSubtypes = classType.get.knownDirectSubclasses.to[List] val subtypes = genericSubtypes.map { sub => - val typeArgs = sub.asType.typeSignature.baseType(genericType.typeSymbol).typeArgs - val mapping = typeArgs.zip(genericType.typeArgs).toMap - val newTypeParams = sub.asType.toType.typeArgs.map(mapping(_)) - appliedType(sub.asType.toType.typeConstructor, newTypeParams) + val subType = sub.asType.toType // FIXME: Broken for path dependent types + val typeParams = sub.asType.typeParams + val typeArgs = thisType(sub).baseType(genericType.typeSymbol).typeArgs + val mapping = (typeArgs.map(_.typeSymbol), genericType.typeArgs).zipped.toMap + val newTypeArgs = typeParams.map(mapping.withDefault(_.asType.toType)) + val applied = appliedType(subType.typeConstructor, newTypeArgs) + existentialAbstraction(typeParams, applied) } if (subtypes.isEmpty) { diff --git a/examples/shared/src/main/scala/show.scala b/examples/shared/src/main/scala/show.scala index 50b34ee..9f634ba 100644 --- a/examples/shared/src/main/scala/show.scala +++ b/examples/shared/src/main/scala/show.scala @@ -60,4 +60,10 @@ object Show extends GenericShow[String] { implicit val int: Show[String, Int] = new Show[String, Int] { def show(s: Int): String = s.toString } + + /** show typeclass for sequences */ + implicit def seq[A](implicit A: Show[String, A]): Show[String, Seq[A]] = + new Show[String, Seq[A]] { + def show(as: Seq[A]): String = as.iterator.map(A.show).mkString("[", ",", "]") + } } diff --git a/tests/src/main/scala/tests.scala b/tests/src/main/scala/tests.scala index 4b0e5c1..bc33969 100644 --- a/tests/src/main/scala/tests.scala +++ b/tests/src/main/scala/tests.scala @@ -47,6 +47,14 @@ object Test { def apply(a: String, b: String): Test = Test(Param(a, b)) } +sealed trait Politician[+S] +case class Accountable[+S](slogan: S) extends Politician[S] +case class Corrupt[+S, +L <: Seq[Company]](slogan: S, lobby: L) extends Politician[S] + +sealed trait Box[+A] +case class SimpleBox[+A](value: A) extends Box[A] +case class LabelledBox[+A, L <: String](value: A, var label: L) extends Box[A] + object Tests extends TestApp { def tests() = for (i <- 1 to 1000) { @@ -201,5 +209,20 @@ object Tests extends TestApp { test("serialize a value class") { Show.gen[Length].show(new Length(100)) }.assert(_ == "100") + + // Corrupt being covariant in L <: Seq[Company] enables the derivation for Corrupt[String, _] + test("show a Politician with covariant lobby") { + Show.gen[Politician[String]].show(Corrupt("wall", Seq(Company("Alice Inc")))) + }.assert(_ == "Corrupt(slogan=wall,lobby=[Company(name=Alice Inc)])") + + // LabelledBox being invariant in L <: String prohibits the derivation for LabelledBox[Int, _] + test("can't show a Box with invariant label") { + scalac"Show.gen[Box[Int]]" + }.assert { _ == TypecheckError( + txt"""magnolia: could not find typeclass for type L + | in parameter 'label' of product type magnolia.tests.LabelledBox[Int, _ <: String] + | in coproduct type magnolia.tests.Box[Int] + |""") + } } } -- cgit v1.2.3 From 6cdb7d145f2784712bc5b56824cbc7f04f9a32e4 Mon Sep 17 00:00:00 2001 From: Jon Pretty Date: Thu, 30 Nov 2017 18:50:57 +0000 Subject: Fixed my bad merge I should not attempt merges using the online GitHub editor... --- core/shared/src/main/scala/magnolia.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/shared/src/main/scala/magnolia.scala b/core/shared/src/main/scala/magnolia.scala index 492d27f..a748561 100644 --- a/core/shared/src/main/scala/magnolia.scala +++ b/core/shared/src/main/scala/magnolia.scala @@ -251,7 +251,7 @@ object Magnolia { val caseParamsReversed = caseClassParameters.foldLeft[List[CaseParam]](Nil) { (acc, param) => val paramName = param.name.decodedName.toString - val paramTypeSubstituted = param.returnType.typeSignatureIn(genericType).resultType + val paramTypeSubstituted = param.typeSignatureIn(genericType).resultType val (repeated, paramType) = paramTypeSubstituted match { case TypeRef(_, `repeatedParamClass`, typeArgs) => -- cgit v1.2.3