Skip to content

Support implicit scope augmentation for Mirror #6879

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

Merged
merged 6 commits into from
Jul 25, 2019
Merged
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
164 changes: 89 additions & 75 deletions compiler/src/dotty/tools/dotc/typer/Implicits.scala
Original file line number Diff line number Diff line change
Expand Up @@ -884,15 +884,15 @@ trait Implicits { self: Typer =>
*
* <parent> {
* MirroredMonoType = <monoType>
* MirroredTypeConstrictor = <tycon>
* MirroredType = <mirroredType>
* MirroredLabel = <label> }
* }
*/
private def mirrorCore(parentClass: ClassSymbol, monoType: Type, mirroredType: Type, label: Name)(implicit ctx: Context) = {
parentClass.typeRef
private def mirrorCore(parentClass: ClassSymbol, monoType: Type, mirroredType: Type, label: Name, formal: Type)(implicit ctx: Context) =
formal & parentClass.typeRef
.refinedWith(tpnme.MirroredMonoType, TypeAlias(monoType))
.refinedWith(tpnme.MirroredType, TypeAlias(mirroredType))
.refinedWith(tpnme.MirroredLabel, TypeAlias(ConstantType(Constant(label.toString))))
}

/** A path referencing the companion of class type `clsType` */
private def companionPath(clsType: Type, span: Span)(implicit ctx: Context) = {
Expand All @@ -901,6 +901,16 @@ trait Implicits { self: Typer =>
ref.withSpan(span)
}

private def checkFormal(formal: Type)(implicit ctx: Context): Boolean = {
@tailrec
def loop(tp: Type): Boolean = tp match {
case RefinedType(parent, _, _: TypeBounds) => loop(parent)
case RefinedType(_, _, _) => false
case _ => true
}
loop(formal)
}

/** An implied instance for a type of the form `Mirror.Product { type MirroredType = T }`
* where `T` is a generic product type or a case object or an enum case.
*/
Expand All @@ -916,12 +926,12 @@ trait Implicits { self: Typer =>
val module = mirroredType.termSymbol
val modulePath = pathFor(mirroredType).withSpan(span)
if (module.info.classSymbol.is(Scala2x)) {
val mirrorType = mirrorCore(defn.Mirror_SingletonProxyClass, mirroredType, mirroredType, module.name)
val mirrorType = mirrorCore(defn.Mirror_SingletonProxyClass, mirroredType, mirroredType, module.name, formal)
val mirrorRef = New(defn.Mirror_SingletonProxyClass.typeRef, modulePath :: Nil)
mirrorRef.cast(mirrorType)
}
else {
val mirrorType = mirrorCore(defn.Mirror_SingletonClass, mirroredType, mirroredType, module.name)
val mirrorType = mirrorCore(defn.Mirror_SingletonClass, mirroredType, mirroredType, module.name, formal)
modulePath.cast(mirrorType)
}
}
Expand All @@ -943,7 +953,7 @@ trait Implicits { self: Typer =>
(mirroredType, elems)
}
val mirrorType =
mirrorCore(defn.Mirror_ProductClass, monoType, mirroredType, cls.name)
mirrorCore(defn.Mirror_ProductClass, monoType, mirroredType, cls.name, formal)
.refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType))
.refinedWith(tpnme.MirroredElemLabels, TypeAlias(TypeOps.nestedPairs(elemLabels)))
val mirrorRef =
Expand All @@ -955,85 +965,89 @@ trait Implicits { self: Typer =>
}
}

formal.member(tpnme.MirroredType).info match {
case TypeBounds(mirroredType, _) => mirrorFor(mirroredType)
case other => EmptyTree
}
if (!checkFormal(formal)) EmptyTree
else
formal.member(tpnme.MirroredType).info match {
case TypeBounds(mirroredType, _) => mirrorFor(mirroredType)
case other => EmptyTree
}
}

/** An implied instance for a type of the form `Mirror.Sum { type MirroredType = T }`
* where `T` is a generic sum type.
*/
lazy val synthesizedSumMirror: SpecialHandler =
(formal, span) => implicit ctx => {
formal.member(tpnme.MirroredType).info match {
case TypeBounds(mirroredType0, _) =>
val mirroredType = mirroredType0.stripTypeVar
if (mirroredType.classSymbol.isGenericSum) {
val cls = mirroredType.classSymbol
val elemLabels = cls.children.map(c => ConstantType(Constant(c.name.toString)))

def solve(sym: Symbol): Type = sym match {
case caseClass: ClassSymbol =>
assert(caseClass.is(Case))
if (caseClass.is(Module))
caseClass.sourceModule.termRef
else {
caseClass.primaryConstructor.info match {
case info: PolyType =>
// Compute the the full child type by solving the subtype constraint
// `C[X1, ..., Xn] <: P`, where
//
// - P is the current `mirroredType`
// - C is the child class, with type parameters X1, ..., Xn
//
// Contravariant type parameters are minimized, all other type parameters are maximized.
def instantiate(implicit ctx: Context) = {
val poly = constrained(info, untpd.EmptyTree)._1
val resType = poly.finalResultType
val target = mirroredType match {
case tp: HKTypeLambda => tp.resultType
case tp => tp
if (!checkFormal(formal)) EmptyTree
else
formal.member(tpnme.MirroredType).info match {
case TypeBounds(mirroredType0, _) =>
val mirroredType = mirroredType0.stripTypeVar
if (mirroredType.classSymbol.isGenericSum) {
val cls = mirroredType.classSymbol
val elemLabels = cls.children.map(c => ConstantType(Constant(c.name.toString)))

def solve(sym: Symbol): Type = sym match {
case caseClass: ClassSymbol =>
assert(caseClass.is(Case))
if (caseClass.is(Module))
caseClass.sourceModule.termRef
else {
caseClass.primaryConstructor.info match {
case info: PolyType =>
// Compute the the full child type by solving the subtype constraint
// `C[X1, ..., Xn] <: P`, where
//
// - P is the current `mirroredType`
// - C is the child class, with type parameters X1, ..., Xn
//
// Contravariant type parameters are minimized, all other type parameters are maximized.
def instantiate(implicit ctx: Context) = {
val poly = constrained(info, untpd.EmptyTree)._1
val resType = poly.finalResultType
val target = mirroredType match {
case tp: HKTypeLambda => tp.resultType
case tp => tp
}
resType <:< target
val tparams = poly.paramRefs
val variances = caseClass.typeParams.map(_.paramVariance)
val instanceTypes = (tparams, variances).zipped.map((tparam, variance) =>
ctx.typeComparer.instanceType(tparam, fromBelow = variance < 0))
resType.substParams(poly, instanceTypes)
}
resType <:< target
val tparams = poly.paramRefs
val variances = caseClass.typeParams.map(_.paramVariance)
val instanceTypes = (tparams, variances).zipped.map((tparam, variance) =>
ctx.typeComparer.instanceType(tparam, fromBelow = variance < 0))
resType.substParams(poly, instanceTypes)
}
instantiate(ctx.fresh.setExploreTyperState().setOwner(caseClass))
case _ =>
caseClass.typeRef
instantiate(ctx.fresh.setExploreTyperState().setOwner(caseClass))
case _ =>
caseClass.typeRef
}
}
}
case child => child.termRef
}
case child => child.termRef
}

val (monoType, elemsType) = mirroredType match {
case mirroredType: HKTypeLambda =>
val elems = mirroredType.derivedLambdaType(
resType = TypeOps.nestedPairs(cls.children.map(solve))
)
val AppliedType(tycon, _) = mirroredType.resultType
val monoType = AppliedType(tycon, mirroredType.paramInfos)
(monoType, elems)
case _ =>
val elems = TypeOps.nestedPairs(cls.children.map(solve))
(mirroredType, elems)
}
val (monoType, elemsType) = mirroredType match {
case mirroredType: HKTypeLambda =>
val elems = mirroredType.derivedLambdaType(
resType = TypeOps.nestedPairs(cls.children.map(solve))
)
val AppliedType(tycon, _) = mirroredType.resultType
val monoType = AppliedType(tycon, mirroredType.paramInfos)
(monoType, elems)
case _ =>
val elems = TypeOps.nestedPairs(cls.children.map(solve))
(mirroredType, elems)
}

val mirrorType =
mirrorCore(defn.Mirror_SumClass, monoType, mirroredType, cls.name)
.refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType))
.refinedWith(tpnme.MirroredElemLabels, TypeAlias(TypeOps.nestedPairs(elemLabels)))
val mirrorRef =
if (cls.linkedClass.exists && !cls.is(Scala2x)) companionPath(mirroredType, span)
else anonymousMirror(monoType, ExtendsSumMirror, span)
mirrorRef.cast(mirrorType)
} else EmptyTree
case _ => EmptyTree
}
val mirrorType =
mirrorCore(defn.Mirror_SumClass, monoType, mirroredType, cls.name, formal)
.refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType))
.refinedWith(tpnme.MirroredElemLabels, TypeAlias(TypeOps.nestedPairs(elemLabels)))
val mirrorRef =
if (cls.linkedClass.exists && !cls.is(Scala2x)) companionPath(mirroredType, span)
else anonymousMirror(monoType, ExtendsSumMirror, span)
mirrorRef.cast(mirrorType)
} else EmptyTree
case _ => EmptyTree
}
}

/** An implied instance for a type of the form `Mirror { type MirroredType = T }`
Expand Down
15 changes: 15 additions & 0 deletions tests/neg/mirror-implicit-scope.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import scala.deriving._

object Test {
class SomeClass
case class ISB(i: Int, s: String, b: Boolean)
case class BI(b: Boolean, i: Int)

val v0 = the[Mirror.ProductOf[ISB]] // OK
val v1 = the[SomeClass & Mirror.ProductOf[ISB]] // error
val v2 = the[Mirror.ProductOf[ISB] & Mirror.ProductOf[BI]] // error
val v3 = the[Mirror.Product { type MirroredType = ISB ; def foo: Int }] // error
val v4 = the[Mirror.Product { type MirroredType = ISB ; def foo(i: Int): Int }] // error
val v5 = the[Mirror.Product { type MirroredType = ISB ; def foo[T](t: T): T }] // error // error
val v6 = the[Mirror.Product { type MirroredType = ISB ; val foo: Int }] // error
}
27 changes: 27 additions & 0 deletions tests/pos/mirror-implicit-scope.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import scala.deriving._

object Test {
object K0 {
type Generic[T] = Mirror { type Scope = K0.type ; type MirroredType = T ; type MirroredElemTypes }
given Ops {
inline def (gen: Generic[T]) toRepr[T <: Product](t: T): gen.MirroredElemTypes = Tuple.fromProduct(t).asInstanceOf
}
}

object K1 {
type Generic[F[_]] = Mirror { type Scope = K1.type ; type MirroredType = F ; type MirroredElemTypes[_] }
given Ops {
inline def (gen: Generic[F]) toRepr[F[_] <: Product, T](t: F[T]): gen.MirroredElemTypes[T] = Tuple.fromProduct(t).asInstanceOf
}
}

case class ISB(i: Int, s: String, b: Boolean)
val v0 = the[K0.Generic[ISB]]
val v1 = v0.toRepr(ISB(23, "foo", true))
val v2: (Int, String, Boolean) = v1

case class Box[T](t: T)
val v3 = the[K1.Generic[Box]]
val v4 = v3.toRepr(Box(23))
val v5: Tuple1[Int] = v4
}