Skip to content

Commit 9d2d194

Browse files
authored
Merge pull request #15192 from dotty-staging/fix-15190
support intersection types in mirrorCompanionRef
2 parents 9220b7e + 02ed36c commit 9d2d194

File tree

6 files changed

+206
-52
lines changed

6 files changed

+206
-52
lines changed

compiler/src/dotty/tools/dotc/transform/TypeUtils.scala

+5-5
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,11 @@ object TypeUtils {
8787
* of this type, while keeping the same prefix.
8888
*/
8989
def mirrorCompanionRef(using Context): TermRef = self match {
90-
case OrType(tp1, tp2) =>
91-
val r1 = tp1.mirrorCompanionRef
92-
val r2 = tp2.mirrorCompanionRef
93-
assert(r1.symbol == r2.symbol, em"mirrorCompanionRef mismatch for $self: $r1, $r2 did not have the same symbol")
94-
r1
90+
case AndType(tp1, tp2) =>
91+
val c1 = tp1.classSymbol
92+
val c2 = tp2.classSymbol
93+
if c1.isSubClass(c2) then tp1.mirrorCompanionRef
94+
else tp2.mirrorCompanionRef // precondition: the parts of the AndType have already been checked to be non-overlapping
9595
case self @ TypeRef(prefix, _) if self.symbol.isClass =>
9696
prefix.select(self.symbol.companionModule).asInstanceOf[TermRef]
9797
case self: TypeProxy =>

compiler/src/dotty/tools/dotc/typer/Synthesizer.scala

+93-47
Original file line numberDiff line numberDiff line change
@@ -276,16 +276,83 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
276276
case t => mapOver(t)
277277
monoMap(mirroredType.resultType)
278278

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

281-
def whyNotAcceptableType(tp: Type, cls: Symbol): String = tp match
283+
/** A comparison that chooses the most specific MirrorSource, this is guided by what is necessary for
284+
* `Mirror.Product.fromProduct`. i.e. its result type should be compatible with the erasure of `mirroredType`.
285+
*/
286+
def isSub(that: MirrorSource)(using Context): Boolean =
287+
(this, that) match
288+
case (Singleton(src, _), ClassSymbol(cls)) => src.info.classSymbol.isSubClass(cls)
289+
case (ClassSymbol(cls1), ClassSymbol(cls2)) => cls1.isSubClass(cls2)
290+
case (Singleton(src1, _), Singleton(src2, _)) => src1 eq src2
291+
case (_: ClassSymbol, _: Singleton) => false
292+
293+
def show(using Context): String = this match
294+
case ClassSymbol(cls) => i"$cls"
295+
case Singleton(src, _) => i"$src"
296+
297+
private[Synthesizer] object MirrorSource:
298+
299+
/** Reduces a mirroredType to either its most specific ClassSymbol,
300+
* or a TermRef to a singleton value. These are
301+
* the base elements required to generate a mirror.
302+
*/
303+
def reduce(mirroredType: Type)(using Context): Either[String, MirrorSource] = mirroredType match
304+
case tp: TypeRef =>
305+
val sym = tp.symbol
306+
if sym.isClass then // direct ref to a class, not an alias
307+
if sym.isAllOf(Case | Module) then
308+
// correct widened module ref. Tested in tests/run/i15234.scala
309+
val singleton = sym.sourceModule
310+
Right(MirrorSource.Singleton(singleton, TermRef(tp.prefix, singleton)))
311+
else
312+
Right(MirrorSource.ClassSymbol(sym))
313+
else
314+
reduce(tp.superType)
315+
case tp: TermRef =>
316+
/** Dealias a path type to extract the underlying definition when it is either
317+
* a singleton enum case or a case object.
318+
*/
319+
def reduceToEnumOrCaseObject(tp: Type)(using Context): Symbol = tp match
320+
case tp: TermRef =>
321+
val sym = tp.termSymbol
322+
if sym.isEnumCase || (sym.isClass && sym.isAllOf(Case | Module)) then sym
323+
else if sym.exists && !tp.isOverloaded then reduceToEnumOrCaseObject(tp.underlying.widenExpr)
324+
else NoSymbol
325+
case _ => NoSymbol
326+
327+
// capture enum singleton types. Tested in tests/run/i15234.scala
328+
val singleton = reduceToEnumOrCaseObject(tp)
329+
if singleton.exists then
330+
Right(MirrorSource.Singleton(singleton, tp))
331+
else
332+
reduce(tp.underlying)
282333
case tp: HKTypeLambda if tp.resultType.isInstanceOf[HKTypeLambda] =>
283-
i"its subpart `$tp` is not a supported kind (either `*` or `* -> *`)"
284-
case tp: TypeProxy => whyNotAcceptableType(tp.underlying, cls)
285-
case OrType(tp1, tp2) => i"its subpart `$tp` is a top-level union type."
286-
case _ =>
287-
if tp.classSymbol eq cls then ""
288-
else i"a subpart reduces to the more precise ${tp.classSymbol}, expected $cls"
334+
Left(i"its subpart `$tp` is not a supported kind (either `*` or `* -> *`)")
335+
case tp: TypeProxy =>
336+
reduce(tp.underlying)
337+
case tp @ AndType(l, r) =>
338+
for
339+
lsrc <- reduce(l)
340+
rsrc <- reduce(r)
341+
res <- locally {
342+
if lsrc.isSub(rsrc) then Right(lsrc)
343+
else if rsrc.isSub(lsrc) then Right(rsrc)
344+
else Left(i"its subpart `$tp` is an intersection of unrelated definitions ${lsrc.show} and ${rsrc.show}.")
345+
}
346+
yield
347+
res
348+
case tp: OrType =>
349+
Left(i"its subpart `$tp` is a top-level union type.")
350+
case tp =>
351+
Left(i"its subpart `$tp` is an unsupported type.")
352+
353+
end MirrorSource
354+
355+
private def productMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors =
289356

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

312-
/** widen TermRef to see if they are an alias to an enum singleton */
313-
def isEnumSingletonRef(tp: Type)(using Context): Boolean = tp match
314-
case tp: TermRef =>
315-
val sym = tp.termSymbol
316-
sym.isEnumCase || (!tp.isOverloaded && isEnumSingletonRef(tp.underlying.widenExpr))
317-
case _ => false
318-
319-
mirroredType match
320-
case AndType(tp1, tp2) =>
321-
orElse(productMirror(tp1, formal, span), productMirror(tp2, formal, span))
322-
case _ =>
323-
val cls = mirroredType.classSymbol
324-
if isEnumSingletonRef(mirroredType) || cls.isAllOf(Case | Module) then
325-
val (singleton, singletonRef) =
326-
if mirroredType.termSymbol.exists then (mirroredType.termSymbol, mirroredType)
327-
else (cls.sourceModule, cls.sourceModule.reachableTermRef)
328-
val singletonPath = pathFor(singletonRef).withSpan(span)
329-
if singleton.info.classSymbol.is(Scala2x) then // could be Scala 3 alias of Scala 2 case object.
330-
val mirrorType = mirrorCore(defn.Mirror_SingletonProxyClass, mirroredType, mirroredType, singleton.name, formal)
379+
MirrorSource.reduce(mirroredType) match
380+
case Right(msrc) => msrc match
381+
case MirrorSource.Singleton(_, tref) =>
382+
val singleton = tref.termSymbol // prefer alias name over the orignal name
383+
val singletonPath = pathFor(tref).withSpan(span)
384+
if tref.classSymbol.is(Scala2x) then // could be Scala 3 alias of Scala 2 case object.
385+
val mirrorType =
386+
mirrorCore(defn.Mirror_SingletonProxyClass, mirroredType, mirroredType, singleton.name, formal)
331387
val mirrorRef = New(defn.Mirror_SingletonProxyClass.typeRef, singletonPath :: Nil)
332388
withNoErrors(mirrorRef.cast(mirrorType))
333389
else
334390
val mirrorType = mirrorCore(defn.Mirror_SingletonClass, mirroredType, mirroredType, singleton.name, formal)
335391
withNoErrors(singletonPath.cast(mirrorType))
336-
else
337-
val acceptableMsg = whyNotAcceptableType(mirroredType, cls)
338-
if acceptableMsg.isEmpty then
339-
if cls.isGenericProduct then makeProductMirror(cls)
340-
else withErrors(i"$cls is not a generic product because ${cls.whyNotGenericProduct}")
341-
else withErrors(i"type `$mirroredType` is not a generic product because $acceptableMsg")
392+
case MirrorSource.ClassSymbol(cls) =>
393+
if cls.isGenericProduct then makeProductMirror(cls)
394+
else withErrors(i"$cls is not a generic product because ${cls.whyNotGenericProduct}")
395+
case Left(msg) =>
396+
withErrors(i"type `$mirroredType` is not a generic product because $msg")
342397
end productMirror
343398

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

346-
val cls = mirroredType.classSymbol
347-
val clsIsGenericSum = cls.isGenericSum
401+
val (acceptableMsg, cls) = MirrorSource.reduce(mirroredType) match
402+
case Right(MirrorSource.Singleton(_, tp)) => (i"its subpart `$tp` is a term reference", NoSymbol)
403+
case Right(MirrorSource.ClassSymbol(cls)) => ("", cls)
404+
case Left(msg) => (msg, NoSymbol)
348405

349-
def whyNotAcceptableType(tp: Type): String = tp match
350-
case tp: TermRef => i"its subpart `$tp` is a term reference"
351-
case tp: HKTypeLambda if tp.resultType.isInstanceOf[HKTypeLambda] =>
352-
i"its subpart `$tp` is not a supported kind (either `*` or `* -> *`)"
353-
case tp: TypeProxy => whyNotAcceptableType(tp.underlying)
354-
case OrType(tp1, tp2) => i"its subpart `$tp` is a top-level union type."
355-
case _ =>
356-
if tp.classSymbol eq cls then ""
357-
else i"a subpart reduces to the more precise ${tp.classSymbol}, expected $cls"
358-
359-
360-
val acceptableMsg = whyNotAcceptableType(mirroredType)
406+
val clsIsGenericSum = cls.isGenericSum
361407

362408
if acceptableMsg.isEmpty && clsIsGenericSum then
363409
val elemLabels = cls.children.map(c => ConstantType(Constant(c.name.toString)))

tests/neg/i15190.scala

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import scala.deriving.Mirror
2+
3+
trait Mixin
4+
object Mixin
5+
6+
trait Parent
7+
object Parent
8+
9+
sealed trait Fruit extends Parent
10+
object Fruit {
11+
case object Apple extends Fruit
12+
case object Orange extends Fruit
13+
}
14+
15+
@main def Test = {
16+
summon[Mirror.SumOf[Fruit & Mixin]] // error: not a sum type
17+
}
+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:21:56 --------------------------------------------------------------
2+
21 |val testA = summon[Mirror.ProductOf[Cns[Int] & Sm[Int]]] // error: unreleated
3+
| ^
4+
|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.
5+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:22:56 --------------------------------------------------------------
6+
22 |val testB = summon[Mirror.ProductOf[Sm[Int] & Cns[Int]]] // error: unreleated
7+
| ^
8+
|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.
9+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:23:49 --------------------------------------------------------------
10+
23 |val testC = summon[Mirror.Of[Cns[Int] & Sm[Int]]] // error: unreleated
11+
| ^
12+
|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]]:
13+
| * 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.
14+
| * 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.
15+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:24:49 --------------------------------------------------------------
16+
24 |val testD = summon[Mirror.Of[Sm[Int] & Cns[Int]]] // error: unreleated
17+
| ^
18+
|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]]:
19+
| * 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.
20+
| * 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.
21+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:25:55 --------------------------------------------------------------
22+
25 |val testE = summon[Mirror.ProductOf[Sm[Int] & Nn.type]] // error: unreleated
23+
| ^
24+
|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.
25+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:26:55 --------------------------------------------------------------
26+
26 |val testF = summon[Mirror.ProductOf[Nn.type & Sm[Int]]] // error: unreleated
27+
| ^
28+
|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.
29+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:27:54 --------------------------------------------------------------
30+
27 |val testG = summon[Mirror.Of[Foo.A.type & Foo.B.type]] // error: unreleated
31+
| ^
32+
|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)]:
33+
| * 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.
34+
| * 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.
35+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:28:54 --------------------------------------------------------------
36+
28 |val testH = summon[Mirror.Of[Foo.B.type & Foo.A.type]] // error: unreleated
37+
| ^
38+
|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)]:
39+
| * 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.
40+
| * 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
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import scala.deriving.Mirror
2+
3+
sealed trait Lst[+A] // AKA: scala.collection.immutable.List
4+
case class Cns[+A](head: A, tail: Lst[A]) extends Lst[A]
5+
case object Nl extends Lst[Nothing]
6+
7+
sealed trait Opt[+A] // AKA: scala.Option
8+
case class Sm[+A](value: A) extends Opt[A]
9+
case object Nn extends Opt[Nothing]
10+
11+
enum Foo:
12+
case A, B
13+
14+
object Bar:
15+
val A: Foo.A.type = Foo.A // alias of Foo.A
16+
type A = Foo.A.type // type alias
17+
18+
case object Baz
19+
type Baz = Baz.type
20+
21+
val testA = summon[Mirror.ProductOf[Cns[Int] & Sm[Int]]] // error: unreleated
22+
val testB = summon[Mirror.ProductOf[Sm[Int] & Cns[Int]]] // error: unreleated
23+
val testC = summon[Mirror.Of[Cns[Int] & Sm[Int]]] // error: unreleated
24+
val testD = summon[Mirror.Of[Sm[Int] & Cns[Int]]] // error: unreleated
25+
val testE = summon[Mirror.ProductOf[Sm[Int] & Nn.type]] // error: unreleated
26+
val testF = summon[Mirror.ProductOf[Nn.type & Sm[Int]]] // error: unreleated
27+
val testG = summon[Mirror.Of[Foo.A.type & Foo.B.type]] // error: unreleated
28+
val testH = summon[Mirror.Of[Foo.B.type & Foo.A.type]] // error: unreleated
29+
val testI = summon[Mirror.Of[Foo.A.type & Bar.A.type]] // ok
30+
val testJ = summon[Mirror.Of[Bar.A.type & Foo.A.type]] // ok
31+
val testK = summon[Mirror.Of[Foo.A.type & Bar.A.type & Bar.A]] // ok
32+
val testL = summon[Mirror.Of[Baz & Baz.type]] // ok

tests/run/i15190.scala

+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import scala.deriving.Mirror
2+
3+
trait Mixin
4+
object Mixin
5+
6+
trait Parent
7+
object Parent
8+
9+
sealed trait Fruit extends Parent
10+
object Fruit {
11+
case object Apple extends Fruit
12+
case object Orange extends Fruit
13+
}
14+
15+
@main def Test = {
16+
val mFruit = summon[Mirror.SumOf[Fruit & Parent]]
17+
assert(mFruit.ordinal(Fruit.Apple) == 0)
18+
assert(mFruit.ordinal(Fruit.Orange) == 1)
19+
}

0 commit comments

Comments
 (0)