Skip to content

support intersection types in mirrorCompanionRef #15192

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 4 commits into from
May 30, 2022
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
10 changes: 5 additions & 5 deletions compiler/src/dotty/tools/dotc/transform/TypeUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,11 @@ object TypeUtils {
* of this type, while keeping the same prefix.
*/
def mirrorCompanionRef(using Context): TermRef = self match {
case OrType(tp1, tp2) =>
val r1 = tp1.mirrorCompanionRef
val r2 = tp2.mirrorCompanionRef
assert(r1.symbol == r2.symbol, em"mirrorCompanionRef mismatch for $self: $r1, $r2 did not have the same symbol")
r1
case AndType(tp1, tp2) =>
val c1 = tp1.classSymbol
val c2 = tp2.classSymbol
if c1.isSubClass(c2) then tp1.mirrorCompanionRef
else tp2.mirrorCompanionRef // precondition: the parts of the AndType have already been checked to be non-overlapping
case self @ TypeRef(prefix, _) if self.symbol.isClass =>
prefix.select(self.symbol.companionModule).asInstanceOf[TermRef]
case self: TypeProxy =>
Expand Down
140 changes: 93 additions & 47 deletions compiler/src/dotty/tools/dotc/typer/Synthesizer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -276,16 +276,83 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
case t => mapOver(t)
monoMap(mirroredType.resultType)

private def productMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors =
private[Synthesizer] enum MirrorSource:
case ClassSymbol(cls: Symbol)
case Singleton(src: Symbol, tref: TermRef)

def whyNotAcceptableType(tp: Type, cls: Symbol): String = tp match
/** A comparison that chooses the most specific MirrorSource, this is guided by what is necessary for
* `Mirror.Product.fromProduct`. i.e. its result type should be compatible with the erasure of `mirroredType`.
*/
def isSub(that: MirrorSource)(using Context): Boolean =
(this, that) match
case (Singleton(src, _), ClassSymbol(cls)) => src.info.classSymbol.isSubClass(cls)
case (ClassSymbol(cls1), ClassSymbol(cls2)) => cls1.isSubClass(cls2)
case (Singleton(src1, _), Singleton(src2, _)) => src1 eq src2
case (_: ClassSymbol, _: Singleton) => false

def show(using Context): String = this match
case ClassSymbol(cls) => i"$cls"
case Singleton(src, _) => i"$src"

private[Synthesizer] object MirrorSource:

/** Reduces a mirroredType to either its most specific ClassSymbol,
* or a TermRef to a singleton value. These are
* the base elements required to generate a mirror.
*/
def reduce(mirroredType: Type)(using Context): Either[String, MirrorSource] = mirroredType match
case tp: TypeRef =>
val sym = tp.symbol
if sym.isClass then // direct ref to a class, not an alias
if sym.isAllOf(Case | Module) then
// correct widened module ref. Tested in tests/run/i15234.scala
val singleton = sym.sourceModule
Right(MirrorSource.Singleton(singleton, TermRef(tp.prefix, singleton)))
else
Right(MirrorSource.ClassSymbol(sym))
else
reduce(tp.superType)
case tp: TermRef =>
/** Dealias a path type to extract the underlying definition when it is either
* a singleton enum case or a case object.
*/
def reduceToEnumOrCaseObject(tp: Type)(using Context): Symbol = tp match
case tp: TermRef =>
val sym = tp.termSymbol
if sym.isEnumCase || (sym.isClass && sym.isAllOf(Case | Module)) then sym
else if sym.exists && !tp.isOverloaded then reduceToEnumOrCaseObject(tp.underlying.widenExpr)
else NoSymbol
case _ => NoSymbol

// capture enum singleton types. Tested in tests/run/i15234.scala
val singleton = reduceToEnumOrCaseObject(tp)
if singleton.exists then
Right(MirrorSource.Singleton(singleton, tp))
else
reduce(tp.underlying)
case tp: HKTypeLambda if tp.resultType.isInstanceOf[HKTypeLambda] =>
i"its subpart `$tp` is not a supported kind (either `*` or `* -> *`)"
case tp: TypeProxy => whyNotAcceptableType(tp.underlying, cls)
case OrType(tp1, tp2) => i"its subpart `$tp` is a top-level union type."
case _ =>
if tp.classSymbol eq cls then ""
else i"a subpart reduces to the more precise ${tp.classSymbol}, expected $cls"
Left(i"its subpart `$tp` is not a supported kind (either `*` or `* -> *`)")
case tp: TypeProxy =>
reduce(tp.underlying)
case tp @ AndType(l, r) =>
for
lsrc <- reduce(l)
rsrc <- reduce(r)
res <- locally {
if lsrc.isSub(rsrc) then Right(lsrc)
else if rsrc.isSub(lsrc) then Right(rsrc)
else Left(i"its subpart `$tp` is an intersection of unrelated definitions ${lsrc.show} and ${rsrc.show}.")
}
yield
res
case tp: OrType =>
Left(i"its subpart `$tp` is a top-level union type.")
case tp =>
Left(i"its subpart `$tp` is an unsupported type.")

end MirrorSource

private def productMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors =

def makeProductMirror(cls: Symbol): TreeWithErrors =
val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal))
Expand All @@ -309,55 +376,34 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
withNoErrors(mirrorRef.cast(mirrorType))
end makeProductMirror

/** widen TermRef to see if they are an alias to an enum singleton */
def isEnumSingletonRef(tp: Type)(using Context): Boolean = tp match
case tp: TermRef =>
val sym = tp.termSymbol
sym.isEnumCase || (!tp.isOverloaded && isEnumSingletonRef(tp.underlying.widenExpr))
case _ => false

mirroredType match
case AndType(tp1, tp2) =>
orElse(productMirror(tp1, formal, span), productMirror(tp2, formal, span))
case _ =>
val cls = mirroredType.classSymbol
if isEnumSingletonRef(mirroredType) || cls.isAllOf(Case | Module) then
val (singleton, singletonRef) =
if mirroredType.termSymbol.exists then (mirroredType.termSymbol, mirroredType)
else (cls.sourceModule, cls.sourceModule.reachableTermRef)
val singletonPath = pathFor(singletonRef).withSpan(span)
if singleton.info.classSymbol.is(Scala2x) then // could be Scala 3 alias of Scala 2 case object.
val mirrorType = mirrorCore(defn.Mirror_SingletonProxyClass, mirroredType, mirroredType, singleton.name, formal)
MirrorSource.reduce(mirroredType) match
case Right(msrc) => msrc match
case MirrorSource.Singleton(_, tref) =>
val singleton = tref.termSymbol // prefer alias name over the orignal name
val singletonPath = pathFor(tref).withSpan(span)
if tref.classSymbol.is(Scala2x) then // could be Scala 3 alias of Scala 2 case object.
val mirrorType =
mirrorCore(defn.Mirror_SingletonProxyClass, mirroredType, mirroredType, singleton.name, formal)
val mirrorRef = New(defn.Mirror_SingletonProxyClass.typeRef, singletonPath :: Nil)
withNoErrors(mirrorRef.cast(mirrorType))
else
val mirrorType = mirrorCore(defn.Mirror_SingletonClass, mirroredType, mirroredType, singleton.name, formal)
withNoErrors(singletonPath.cast(mirrorType))
else
val acceptableMsg = whyNotAcceptableType(mirroredType, cls)
if acceptableMsg.isEmpty then
if cls.isGenericProduct then makeProductMirror(cls)
else withErrors(i"$cls is not a generic product because ${cls.whyNotGenericProduct}")
else withErrors(i"type `$mirroredType` is not a generic product because $acceptableMsg")
case MirrorSource.ClassSymbol(cls) =>
if cls.isGenericProduct then makeProductMirror(cls)
else withErrors(i"$cls is not a generic product because ${cls.whyNotGenericProduct}")
case Left(msg) =>
withErrors(i"type `$mirroredType` is not a generic product because $msg")
end productMirror

private def sumMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors =

val cls = mirroredType.classSymbol
val clsIsGenericSum = cls.isGenericSum
val (acceptableMsg, cls) = MirrorSource.reduce(mirroredType) match
case Right(MirrorSource.Singleton(_, tp)) => (i"its subpart `$tp` is a term reference", NoSymbol)
case Right(MirrorSource.ClassSymbol(cls)) => ("", cls)
case Left(msg) => (msg, NoSymbol)

def whyNotAcceptableType(tp: Type): String = tp match
case tp: TermRef => i"its subpart `$tp` is a term reference"
case tp: HKTypeLambda if tp.resultType.isInstanceOf[HKTypeLambda] =>
i"its subpart `$tp` is not a supported kind (either `*` or `* -> *`)"
case tp: TypeProxy => whyNotAcceptableType(tp.underlying)
case OrType(tp1, tp2) => i"its subpart `$tp` is a top-level union type."
case _ =>
if tp.classSymbol eq cls then ""
else i"a subpart reduces to the more precise ${tp.classSymbol}, expected $cls"


val acceptableMsg = whyNotAcceptableType(mirroredType)
val clsIsGenericSum = cls.isGenericSum

if acceptableMsg.isEmpty && clsIsGenericSum then
val elemLabels = cls.children.map(c => ConstantType(Constant(c.name.toString)))
Expand Down
17 changes: 17 additions & 0 deletions tests/neg/i15190.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import scala.deriving.Mirror

trait Mixin
object Mixin

trait Parent
object Parent

sealed trait Fruit extends Parent
object Fruit {
case object Apple extends Fruit
case object Orange extends Fruit
}

@main def Test = {
summon[Mirror.SumOf[Fruit & Mixin]] // error: not a sum type
}
40 changes: 40 additions & 0 deletions tests/neg/mirror-synthesis-errors-b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
-- Error: tests/neg/mirror-synthesis-errors-b.scala:21:56 --------------------------------------------------------------
21 |val testA = summon[Mirror.ProductOf[Cns[Int] & Sm[Int]]] // error: unreleated
| ^
|No given instance of type deriving.Mirror.ProductOf[Cns[Int] & Sm[Int]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.ProductOf[Cns[Int] & Sm[Int]]: type `Cns[Int] & Sm[Int]` is not a generic product because its subpart `Cns[Int] & Sm[Int]` is an intersection of unrelated definitions class Cns and class Sm.
-- Error: tests/neg/mirror-synthesis-errors-b.scala:22:56 --------------------------------------------------------------
22 |val testB = summon[Mirror.ProductOf[Sm[Int] & Cns[Int]]] // error: unreleated
| ^
|No given instance of type deriving.Mirror.ProductOf[Sm[Int] & Cns[Int]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.ProductOf[Sm[Int] & Cns[Int]]: type `Sm[Int] & Cns[Int]` is not a generic product because its subpart `Sm[Int] & Cns[Int]` is an intersection of unrelated definitions class Sm and class Cns.
-- Error: tests/neg/mirror-synthesis-errors-b.scala:23:49 --------------------------------------------------------------
23 |val testC = summon[Mirror.Of[Cns[Int] & Sm[Int]]] // error: unreleated
| ^
|No given instance of type deriving.Mirror.Of[Cns[Int] & Sm[Int]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[Cns[Int] & Sm[Int]]:
| * type `Cns[Int] & Sm[Int]` is not a generic product because its subpart `Cns[Int] & Sm[Int]` is an intersection of unrelated definitions class Cns and class Sm.
| * type `Cns[Int] & Sm[Int]` is not a generic sum because its subpart `Cns[Int] & Sm[Int]` is an intersection of unrelated definitions class Cns and class Sm.
-- Error: tests/neg/mirror-synthesis-errors-b.scala:24:49 --------------------------------------------------------------
24 |val testD = summon[Mirror.Of[Sm[Int] & Cns[Int]]] // error: unreleated
| ^
|No given instance of type deriving.Mirror.Of[Sm[Int] & Cns[Int]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[Sm[Int] & Cns[Int]]:
| * type `Sm[Int] & Cns[Int]` is not a generic product because its subpart `Sm[Int] & Cns[Int]` is an intersection of unrelated definitions class Sm and class Cns.
| * type `Sm[Int] & Cns[Int]` is not a generic sum because its subpart `Sm[Int] & Cns[Int]` is an intersection of unrelated definitions class Sm and class Cns.
-- Error: tests/neg/mirror-synthesis-errors-b.scala:25:55 --------------------------------------------------------------
25 |val testE = summon[Mirror.ProductOf[Sm[Int] & Nn.type]] // error: unreleated
| ^
|No given instance of type deriving.Mirror.ProductOf[Sm[Int] & Nn.type] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.ProductOf[Sm[Int] & Nn.type]: type `Sm[Int] & Nn.type` is not a generic product because its subpart `Sm[Int] & Nn.type` is an intersection of unrelated definitions class Sm and object Nn.
-- Error: tests/neg/mirror-synthesis-errors-b.scala:26:55 --------------------------------------------------------------
26 |val testF = summon[Mirror.ProductOf[Nn.type & Sm[Int]]] // error: unreleated
| ^
|No given instance of type deriving.Mirror.ProductOf[Nn.type & Sm[Int]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.ProductOf[Nn.type & Sm[Int]]: type `Nn.type & Sm[Int]` is not a generic product because its subpart `Nn.type & Sm[Int]` is an intersection of unrelated definitions object Nn and class Sm.
-- Error: tests/neg/mirror-synthesis-errors-b.scala:27:54 --------------------------------------------------------------
27 |val testG = summon[Mirror.Of[Foo.A.type & Foo.B.type]] // error: unreleated
| ^
|No given instance of type deriving.Mirror.Of[(Foo.A : Foo) & (Foo.B : Foo)] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[(Foo.A : Foo) & (Foo.B : Foo)]:
| * type `(Foo.A : Foo) & (Foo.B : Foo)` is not a generic product because its subpart `(Foo.A : Foo) & (Foo.B : Foo)` is an intersection of unrelated definitions value A and value B.
| * type `(Foo.A : Foo) & (Foo.B : Foo)` is not a generic sum because its subpart `(Foo.A : Foo) & (Foo.B : Foo)` is an intersection of unrelated definitions value A and value B.
-- Error: tests/neg/mirror-synthesis-errors-b.scala:28:54 --------------------------------------------------------------
28 |val testH = summon[Mirror.Of[Foo.B.type & Foo.A.type]] // error: unreleated
| ^
|No given instance of type deriving.Mirror.Of[(Foo.B : Foo) & (Foo.A : Foo)] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[(Foo.B : Foo) & (Foo.A : Foo)]:
| * type `(Foo.B : Foo) & (Foo.A : Foo)` is not a generic product because its subpart `(Foo.B : Foo) & (Foo.A : Foo)` is an intersection of unrelated definitions value B and value A.
| * type `(Foo.B : Foo) & (Foo.A : Foo)` is not a generic sum because its subpart `(Foo.B : Foo) & (Foo.A : Foo)` is an intersection of unrelated definitions value B and value A.
32 changes: 32 additions & 0 deletions tests/neg/mirror-synthesis-errors-b.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import scala.deriving.Mirror

sealed trait Lst[+A] // AKA: scala.collection.immutable.List
case class Cns[+A](head: A, tail: Lst[A]) extends Lst[A]
case object Nl extends Lst[Nothing]

sealed trait Opt[+A] // AKA: scala.Option
case class Sm[+A](value: A) extends Opt[A]
case object Nn extends Opt[Nothing]

enum Foo:
case A, B

object Bar:
val A: Foo.A.type = Foo.A // alias of Foo.A
type A = Foo.A.type // type alias

case object Baz
type Baz = Baz.type

val testA = summon[Mirror.ProductOf[Cns[Int] & Sm[Int]]] // error: unreleated
val testB = summon[Mirror.ProductOf[Sm[Int] & Cns[Int]]] // error: unreleated
val testC = summon[Mirror.Of[Cns[Int] & Sm[Int]]] // error: unreleated
val testD = summon[Mirror.Of[Sm[Int] & Cns[Int]]] // error: unreleated
val testE = summon[Mirror.ProductOf[Sm[Int] & Nn.type]] // error: unreleated
val testF = summon[Mirror.ProductOf[Nn.type & Sm[Int]]] // error: unreleated
val testG = summon[Mirror.Of[Foo.A.type & Foo.B.type]] // error: unreleated
val testH = summon[Mirror.Of[Foo.B.type & Foo.A.type]] // error: unreleated
val testI = summon[Mirror.Of[Foo.A.type & Bar.A.type]] // ok
val testJ = summon[Mirror.Of[Bar.A.type & Foo.A.type]] // ok
val testK = summon[Mirror.Of[Foo.A.type & Bar.A.type & Bar.A]] // ok
val testL = summon[Mirror.Of[Baz & Baz.type]] // ok
19 changes: 19 additions & 0 deletions tests/run/i15190.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import scala.deriving.Mirror

trait Mixin
object Mixin

trait Parent
object Parent

sealed trait Fruit extends Parent
object Fruit {
case object Apple extends Fruit
case object Orange extends Fruit
}

@main def Test = {
val mFruit = summon[Mirror.SumOf[Fruit & Parent]]
assert(mFruit.ordinal(Fruit.Apple) == 0)
assert(mFruit.ordinal(Fruit.Orange) == 1)
}