Skip to content

Support Mirrors for value classes #7023

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

Closed
Closed
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
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -625,6 +625,8 @@ class Definitions {
@tu lazy val Mirror_SumClass: ClassSymbol = ctx.requiredClass("scala.deriving.Mirror.Sum")
@tu lazy val Mirror_SingletonClass: ClassSymbol = ctx.requiredClass("scala.deriving.Mirror.Singleton")
@tu lazy val Mirror_SingletonProxyClass: ClassSymbol = ctx.requiredClass("scala.deriving.Mirror.SingletonProxy")
@tu lazy val Mirror_ValueClassClass: ClassSymbol = ctx.requiredClass("scala.deriving.Mirror.ValueClass")
@tu lazy val Mirror_ValueClassClass_wrapValue: Symbol = Mirror_ValueClassClass.requiredMethod(nme.wrapValue)

@tu lazy val LanguageModule: Symbol = ctx.requiredModule("scala.language")
@tu lazy val NonLocalReturnControlClass: ClassSymbol = ctx.requiredClass("scala.runtime.NonLocalReturnControl")
Expand Down
1 change: 1 addition & 0 deletions compiler/src/dotty/tools/dotc/core/StdNames.scala
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,7 @@ object StdNames {
val withFilterIfRefutable: N = "withFilterIfRefutable$"
val WorksheetWrapper: N = "WorksheetWrapper"
val wrap: N = "wrap"
val wrapValue: N = "wrapValue"
val writeReplace: N = "writeReplace"
val zero: N = "zero"
val zip: N = "zip"
Expand Down
2 changes: 0 additions & 2 deletions compiler/src/dotty/tools/dotc/transform/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import StdNames._
import NameKinds._
import Flags._
import Annotations._
import ValueClasses.isDerivedValueClass
import Decorators._

import language.implicitConversions
Expand Down Expand Up @@ -79,7 +78,6 @@ class SymUtils(val self: Symbol) extends AnyVal {
if (!self.is(CaseClass)) "it is not a case class"
else if (self.is(Abstract)) "it is an abstract class"
else if (self.primaryConstructor.info.paramInfoss.length != 1) "it takes more than one parameter list"
else if (isDerivedValueClass(self)) "it is a value class"
else ""

def isGenericProduct(implicit ctx: Context): Boolean = whyNotGenericProduct.isEmpty
Expand Down
34 changes: 31 additions & 3 deletions compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,28 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
}
}

def wrapValueBody(caseClass: Symbol, param: Tree)(implicit ctx: Context): Tree = {
val (classRef, methTpe) =
caseClass.primaryConstructor.info match {
case tl: PolyType =>
val (tl1, tpts) = constrained(tl, untpd.EmptyTree, alwaysAddTypeVars = true)
val targs =
for (tpt <- tpts) yield
tpt.tpe match {
case tvar: TypeVar => tvar.instantiate(fromBelow = false)
}
(caseClass.typeRef.appliedTo(targs), tl.instantiate(targs))
case methTpe =>
(caseClass.typeRef, methTpe)
}
methTpe match {
case methTpe: MethodType =>
val formal = methTpe.paramInfos(0)
val elem = param.ensureConforms(formal)
New(classRef, List(elem))
}
}

/** For an enum T:
*
* def ordinal(x: MirroredMonoType) = x.ordinal
Expand Down Expand Up @@ -438,9 +460,15 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
def makeSingletonMirror() =
addParent(defn.Mirror_SingletonClass.typeRef)
def makeProductMirror(cls: Symbol) = {
addParent(defn.Mirror_ProductClass.typeRef)
addMethod(nme.fromProduct, MethodType(defn.ProductClass.typeRef :: Nil, monoType.typeRef), cls,
fromProductBody(_, _)(_).ensureConforms(monoType.typeRef)) // t4758.scala or i3381.scala are examples where a cast is needed
if (isDerivedValueClass(cls)) {
addParent(defn.Mirror_ValueClassClass.typeRef)
addMethod(nme.wrapValue, MethodType(defn.AnyClass.typeRef :: Nil, monoType.typeRef), cls,
wrapValueBody(_, _)(_).ensureConforms(monoType.typeRef))
} else {
addParent(defn.Mirror_ProductClass.typeRef)
addMethod(nme.fromProduct, MethodType(defn.ProductClass.typeRef :: Nil, monoType.typeRef), cls,
fromProductBody(_, _)(_).ensureConforms(monoType.typeRef)) // t4758.scala or i3381.scala are examples where a cast is needed
}
}
def makeSumMirror(cls: Symbol) = {
addParent(defn.Mirror_SumClass.typeRef)
Expand Down
4 changes: 3 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import Trees._
import transform.SymUtils._
import transform.TypeUtils._
import transform.SyntheticMembers._
import transform.ValueClasses.isDerivedValueClass
import Hashable._
import util.{Property, SourceFile, NoSource}
import config.Config
Expand Down Expand Up @@ -960,8 +961,9 @@ trait Implicits { self: Typer =>
val elems = TypeOps.nestedPairs(accessors.map(mirroredType.memberInfo(_).widenExpr))
(mirroredType, elems)
}
val mirrorClass = if (isDerivedValueClass(cls)) defn.Mirror_ValueClassClass else defn.Mirror_ProductClass
val mirrorType =
mirrorCore(defn.Mirror_ProductClass, monoType, mirroredType, cls.name, formal)
mirrorCore(mirrorClass, monoType, mirroredType, cls.name, formal)
.refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType))
.refinedWith(tpnme.MirroredElemLabels, TypeAlias(TypeOps.nestedPairs(elemLabels)))
val mirrorRef =
Expand Down
7 changes: 6 additions & 1 deletion library/src/scala/deriving.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ object deriving {

/** The Mirror for a product type */
trait Product extends Mirror {

/** Create a new instance of type `T` with elements taken from product `p`. */
def fromProduct(p: scala.Product): MirroredMonoType
}
Expand All @@ -48,6 +47,12 @@ object deriving {
def fromProduct(p: scala.Product) = value
}

trait ValueClass extends Product {
type MirroredMonoType <: scala.Product
def fromProduct(p: scala.Product): MirroredMonoType = wrapValue(p.productElement(0))
def wrapValue(v: Any): MirroredMonoType
}

type Of[T] = Mirror { type MirroredType = T; type MirroredMonoType = T ; type MirroredElemTypes <: Tuple }
type ProductOf[T] = Mirror.Product { type MirroredType = T; type MirroredMonoType = T ; type MirroredElemTypes <: Tuple }
type SumOf[T] = Mirror.Sum { type MirroredType = T; type MirroredMonoType = T; type MirroredElemTypes <: Tuple }
Expand Down
16 changes: 16 additions & 0 deletions tests/run/i7000.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import scala.deriving._
import compiletime._

object Test extends App {
case class B(v: Double) extends AnyVal
val m0 = the[Mirror.ProductOf[B]]

val v0 = m0.fromProduct(Tuple1(23.0))
assert(v0 == B(23.0))

case class C[T](v: T) extends AnyVal
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens for value classes which don't extend product (so don't have case) ?

Copy link
Contributor Author

@milessabin milessabin Aug 12, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently only case classes (value or otherwise) have product mirrors generated automatically, so that case doesn't arise. Someone could provide a mirror for a non-case value class by hand, and then they'd have to deal with the bridge collision themselves.

val m1 = the[Mirror.ProductOf[C[Double]]]

val v1 = m1.fromProduct(Tuple1(23.0))
assert(v1 == C(23.0))
}