Skip to content

Commit 3ea6b24

Browse files
committed
test more intersections of singletons
1 parent 1cbaf7e commit 3ea6b24

File tree

4 files changed

+77
-39
lines changed

4 files changed

+77
-39
lines changed

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

Lines changed: 37 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -278,51 +278,62 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
278278

279279
private[Synthesizer] enum MirrorSource:
280280
case ClassSymbol(cls: Symbol)
281-
case Singleton(tref: TermRef)
281+
case Singleton(src: Symbol, tref: TermRef)
282282

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+
*/
283286
def isSub(that: MirrorSource)(using Context): Boolean =
284287
(this, that) match
285-
case (Singleton(tref), ClassSymbol(cls)) => tref.classSymbol.isSubClass(cls)
288+
case (Singleton(src, _), ClassSymbol(cls)) => src.info.classSymbol.isSubClass(cls)
286289
case (ClassSymbol(cls1), ClassSymbol(cls2)) => cls1.isSubClass(cls2)
287-
case _ => false // includes that a class is never sub of a singleton
290+
case (Singleton(src1, _), Singleton(src2, _)) => src1 eq src2
291+
case (_: ClassSymbol, _: Singleton) => false
288292

289293
def debug(using Context): String = this match
290294
case ClassSymbol(cls) => i"$cls"
291-
case Singleton(tref) => i"${tref.termSymbol}"
295+
case Singleton(src, _) => i"$src"
292296

293297
object MirrorSource:
294-
def cls(cls: Symbol): MirrorSource = ClassSymbol(cls)
295-
def singleton(tref: TermRef): MirrorSource = Singleton(tref)
296-
297-
/** widen TermRef to see if they are an alias to an enum singleton or case object */
298-
private def isEnumOrCaseObjectRef(tp: Type)(using Context): Boolean = tp match
299-
case tp: TermRef =>
300-
val sym = tp.termSymbol
301-
302-
sym.isEnumCase
303-
|| (sym.isClass && sym.isAllOf(Case | Module))
304-
|| (!tp.isOverloaded && isEnumOrCaseObjectRef(tp.underlying.widenExpr))
305-
case _ => false
306298

307-
/** A customised version of `Types.classSymbol`, specialised for mirror generation. */
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+
*/
308303
def reduce(mirroredType: Type)(using Context): Either[String, MirrorSource] = mirroredType match
309304
case tp: TypeRef =>
310305
val sym = tp.symbol
311306
if sym.isClass then // direct ref to a class, not an alias
312307
if sym.isAllOf(Case | Module) then
313-
Right(MirrorSource.singleton(sym.sourceModule.reachableTermRef)) // correct widened module ref
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)))
314311
else
315-
Right(MirrorSource.cls(sym))
312+
Right(MirrorSource.ClassSymbol(sym))
316313
else
317314
reduce(tp.superType)
318-
case tp: TermRef if isEnumOrCaseObjectRef(tp) =>
319-
Right(MirrorSource.singleton(tp))
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)
320333
case tp: HKTypeLambda if tp.resultType.isInstanceOf[HKTypeLambda] =>
321334
Left(i"its subpart `$tp` is not a supported kind (either `*` or `* -> *`)")
322335
case tp: TypeProxy =>
323336
reduce(tp.underlying)
324-
case tp: ClassInfo =>
325-
Right(MirrorSource.cls(tp.cls))
326337
case tp @ AndType(l, r) =>
327338
for
328339
lsrc <- reduce(l)
@@ -367,8 +378,8 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
367378

368379
MirrorSource.reduce(mirroredType) match
369380
case Right(msrc) => msrc match
370-
case MirrorSource.Singleton(tref) =>
371-
val singleton = tref.termSymbol
381+
case MirrorSource.Singleton(_, tref) =>
382+
val singleton = tref.termSymbol // prefer alias name over the orignal name
372383
val singletonPath = pathFor(tref).withSpan(span)
373384
if tref.classSymbol.is(Scala2x) then // could be Scala 3 alias of Scala 2 case object.
374385
val mirrorType =
@@ -388,7 +399,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
388399
private def sumMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors =
389400

390401
val (acceptableMsg, cls) = MirrorSource.reduce(mirroredType) match
391-
case Right(MirrorSource.Singleton(tp)) => (i"its subpart `$tp` is a term reference", NoSymbol)
402+
case Right(MirrorSource.Singleton(_, tp)) => (i"its subpart `$tp` is a term reference", NoSymbol)
392403
case Right(MirrorSource.ClassSymbol(cls)) => ("", cls)
393404
case Left(msg) => (msg, NoSymbol)
394405

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,40 @@
1-
-- Error: tests/neg/mirror-synthesis-errors-b.scala:11:56 --------------------------------------------------------------
2-
11 |val testA = summon[Mirror.ProductOf[Cns[Int] & Sm[Int]]] // error: unreleated
1+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:21:56 --------------------------------------------------------------
2+
21 |val testA = summon[Mirror.ProductOf[Cns[Int] & Sm[Int]]] // error: unreleated
33
| ^
44
|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 symbols class Cns and class Sm.
5-
-- Error: tests/neg/mirror-synthesis-errors-b.scala:12:56 --------------------------------------------------------------
6-
12 |val testB = summon[Mirror.ProductOf[Sm[Int] & Cns[Int]]] // error: unreleated
5+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:22:56 --------------------------------------------------------------
6+
22 |val testB = summon[Mirror.ProductOf[Sm[Int] & Cns[Int]]] // error: unreleated
77
| ^
88
|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 symbols class Sm and class Cns.
9-
-- Error: tests/neg/mirror-synthesis-errors-b.scala:13:49 --------------------------------------------------------------
10-
13 |val testC = summon[Mirror.Of[Cns[Int] & Sm[Int]]] // error: unreleated
9+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:23:49 --------------------------------------------------------------
10+
23 |val testC = summon[Mirror.Of[Cns[Int] & Sm[Int]]] // error: unreleated
1111
| ^
1212
|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]]:
1313
| * type `Cns[Int] & Sm[Int]` is not a generic product because its subpart `Cns[Int] & Sm[Int]` is an intersection of unrelated symbols class Cns and class Sm.
1414
| * type `Cns[Int] & Sm[Int]` is not a generic sum because its subpart `Cns[Int] & Sm[Int]` is an intersection of unrelated symbols class Cns and class Sm.
15-
-- Error: tests/neg/mirror-synthesis-errors-b.scala:14:49 --------------------------------------------------------------
16-
14 |val testD = summon[Mirror.Of[Sm[Int] & Cns[Int]]] // error: unreleated
15+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:24:49 --------------------------------------------------------------
16+
24 |val testD = summon[Mirror.Of[Sm[Int] & Cns[Int]]] // error: unreleated
1717
| ^
1818
|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]]:
1919
| * type `Sm[Int] & Cns[Int]` is not a generic product because its subpart `Sm[Int] & Cns[Int]` is an intersection of unrelated symbols class Sm and class Cns.
2020
| * type `Sm[Int] & Cns[Int]` is not a generic sum because its subpart `Sm[Int] & Cns[Int]` is an intersection of unrelated symbols class Sm and class Cns.
21-
-- Error: tests/neg/mirror-synthesis-errors-b.scala:15:55 --------------------------------------------------------------
22-
15 |val testE = summon[Mirror.ProductOf[Sm[Int] & Nn.type]] // error: unreleated
21+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:25:55 --------------------------------------------------------------
22+
25 |val testE = summon[Mirror.ProductOf[Sm[Int] & Nn.type]] // error: unreleated
2323
| ^
2424
|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 symbols class Sm and object Nn.
25-
-- Error: tests/neg/mirror-synthesis-errors-b.scala:16:55 --------------------------------------------------------------
26-
16 |val testF = summon[Mirror.ProductOf[Nn.type & Sm[Int]]] // error: unreleated
25+
-- Error: tests/neg/mirror-synthesis-errors-b.scala:26:55 --------------------------------------------------------------
26+
26 |val testF = summon[Mirror.ProductOf[Nn.type & Sm[Int]]] // error: unreleated
2727
| ^
2828
|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 symbols 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 symbols 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 symbols 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 symbols 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 symbols value B and value A.

tests/neg/mirror-synthesis-errors-b.scala

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,25 @@ sealed trait Opt[+A] // AKA: scala.Option
88
case class Sm[+A](value: A) extends Opt[A]
99
case object Nn extends Opt[Nothing]
1010

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+
1121
val testA = summon[Mirror.ProductOf[Cns[Int] & Sm[Int]]] // error: unreleated
1222
val testB = summon[Mirror.ProductOf[Sm[Int] & Cns[Int]]] // error: unreleated
1323
val testC = summon[Mirror.Of[Cns[Int] & Sm[Int]]] // error: unreleated
1424
val testD = summon[Mirror.Of[Sm[Int] & Cns[Int]]] // error: unreleated
1525
val testE = summon[Mirror.ProductOf[Sm[Int] & Nn.type]] // error: unreleated
1626
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/i15234.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ package app {
1313
val Bar: lib.Bar.type = lib.Bar
1414
}
1515

16-
1716
@main def Test =
1817
assert(summon[Mirror.Of[scala.Nil.type]].fromProduct(EmptyTuple) == Nil) // alias scala 2 defined
1918
assert(summon[Mirror.Of[lib.Foo.A.type]].fromProduct(EmptyTuple) == lib.Foo.A) // real mirror

0 commit comments

Comments
 (0)