Skip to content

Commit 3a9f122

Browse files
authored
Merge pull request #11039 from dotty-staging/fix-10997
fix #10997: check accessible from sealed parent not companion
2 parents 83c5b0d + 6a49bf3 commit 3a9f122

File tree

8 files changed

+98
-13
lines changed

8 files changed

+98
-13
lines changed

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

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -87,23 +87,28 @@ object SymUtils:
8787

8888
def isGenericProduct(using Context): Boolean = whyNotGenericProduct.isEmpty
8989

90+
def useCompanionAsMirror(using Context): Boolean = self.linkedClass.exists && !self.is(Scala2x)
91+
9092
/** Is this a sealed class or trait for which a sum mirror is generated?
9193
* It must satisfy the following conditions:
9294
* - it has at least one child class or object
9395
* - none of its children are anonymous classes
94-
* - all of its children are addressable through a path from its companion object
96+
* - all of its children are addressable through a path from the parent class
97+
* and also the location of the generated mirror.
9598
* - all of its children are generic products or singletons
9699
*/
97-
def whyNotGenericSum(using Context): String =
100+
def whyNotGenericSum(declScope: Symbol)(using Context): String =
98101
if (!self.is(Sealed))
99102
s"it is not a sealed ${self.kindString}"
100103
else {
101104
val children = self.children
102-
val companion = self.linkedClass
105+
val companionMirror = self.useCompanionAsMirror
106+
assert(!(companionMirror && (declScope ne self.linkedClass)))
103107
def problem(child: Symbol) = {
104108

105109
def isAccessible(sym: Symbol): Boolean =
106-
companion.isContainedIn(sym) || sym.is(Module) && isAccessible(sym.owner)
110+
(self.isContainedIn(sym) && (companionMirror || declScope.isContainedIn(sym)))
111+
|| sym.is(Module) && isAccessible(sym.owner)
107112

108113
if (child == self) "it has anonymous or inaccessible subclasses"
109114
else if (!isAccessible(child.owner)) i"its child $child is not accessible"
@@ -118,7 +123,7 @@ object SymUtils:
118123
else children.map(problem).find(!_.isEmpty).getOrElse("")
119124
}
120125

121-
def isGenericSum(using Context): Boolean = whyNotGenericSum.isEmpty
126+
def isGenericSum(declScope: Symbol)(using Context): Boolean = whyNotGenericSum(declScope).isEmpty
122127

123128
/** If this is a constructor, its owner: otherwise this. */
124129
final def skipConstructor(using Context): Symbol =

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -584,9 +584,9 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
584584
if (clazz.is(Module)) {
585585
if (clazz.is(Case)) makeSingletonMirror()
586586
else if (linked.isGenericProduct) makeProductMirror(linked)
587-
else if (linked.isGenericSum) makeSumMirror(linked)
587+
else if (linked.isGenericSum(clazz)) makeSumMirror(linked)
588588
else if (linked.is(Sealed))
589-
derive.println(i"$linked is not a sum because ${linked.whyNotGenericSum}")
589+
derive.println(i"$linked is not a sum because ${linked.whyNotGenericSum(clazz)}")
590590
}
591591
else if (impl.removeAttachment(ExtendsSingletonMirror).isDefined)
592592
makeSingletonMirror()

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,10 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
266266
end productMirror
267267

268268
private def sumMirror(mirroredType: Type, formal: Type, span: Span)(using Context): Tree =
269-
if mirroredType.classSymbol.isGenericSum then
270-
val cls = mirroredType.classSymbol
269+
val cls = mirroredType.classSymbol
270+
val useCompanion = cls.useCompanionAsMirror
271+
272+
if cls.isGenericSum(if useCompanion then cls.linkedClass else ctx.owner) then
271273
val elemLabels = cls.children.map(c => ConstantType(Constant(c.name.toString)))
272274

273275
def solve(sym: Symbol): Type = sym match
@@ -318,8 +320,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
318320
.refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType))
319321
.refinedWith(tpnme.MirroredElemLabels, TypeAlias(TypeOps.nestedPairs(elemLabels)))
320322
val mirrorRef =
321-
if cls.linkedClass.exists && !cls.is(Scala2x)
322-
then companionPath(mirroredType, span)
323+
if useCompanion then companionPath(mirroredType, span)
323324
else anonymousMirror(monoType, ExtendsSumMirror, span)
324325
mirrorRef.cast(mirrorType)
325326
else EmptyTree

tests/neg/i10997.scala

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
sealed trait Parent
2+
3+
trait Wrapper {
4+
5+
case class Foo(x: Int, y: Int, s: String) extends Parent
6+
case class Bar(x: Int, y: Int) extends Parent
7+
8+
println(summon[deriving.Mirror.Of[Parent]]) // error
9+
}
10+
11+
class ClassWrapper {
12+
sealed trait Base
13+
case class Foo(x: Int) extends Base
14+
}
15+
16+
@main def Test =
17+
val cw = new ClassWrapper()
18+
val mirrorParent = summon[deriving.Mirror.Of[cw.Base]] // error: code gen for Mirror can not access each case

tests/pos/i10997.scala

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
class Test {
2+
3+
sealed trait Parent
4+
case class Foo(x: Int, y: Int, s: String) extends Parent
5+
case class Bar(x: Int, y: Int) extends Parent
6+
7+
println(summon[deriving.Mirror.Of[Parent]])
8+
}
9+
10+
object Test2 {
11+
12+
case class Foo(x: Int, y: Int, s: String) extends i.Parent
13+
case class Bar(x: Int, y: Int) extends i.Parent
14+
15+
val i = Inner()
16+
17+
class Inner {
18+
19+
sealed trait Parent
20+
21+
println(summon[deriving.Mirror.Of[Parent]])
22+
}
23+
24+
}

tests/run/deriving-constructor-order.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ object Test extends App {
77
case _: T => ()
88
}
99

10-
sealed trait Base1
10+
sealed trait Base1 // Base1 MUST NOT have a companion here!
1111
case class Foo() extends Base1
1212
case object Bar extends Base1
1313
case class Qux(i: Int) extends Base1

tests/run/deriving.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ object T
44
case class A(x: Int, y: Int) extends T
55
case object B extends T
66

7-
sealed trait U
7+
sealed trait U // U MUST NOT have a companion here!
88
case class C() extends U
99

1010
object Test extends App {

tests/run/i10997.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
class ClassWrapper {
2+
3+
sealed trait Parent
4+
case class Foo(x: Int, y: Int, s: String) extends Parent
5+
case class Bar(x: Int, y: Int) extends Parent
6+
case object Qux extends Parent
7+
8+
def testcase(): Unit =
9+
10+
val mirrorParent = summon[deriving.Mirror.Of[Parent]]
11+
val mirrorFoo = summon[deriving.Mirror.Of[Foo]]
12+
val mirrorBar = summon[deriving.Mirror.Of[Bar]]
13+
val mirrorQux = summon[deriving.Mirror.Of[Qux.type]]
14+
15+
val fooShapedTuple: (Int, Int, String) = (23, 47, "ok")
16+
val barShapedTuple: (Int, Int) = (57, 71)
17+
val quxShapedTuple: EmptyTuple = EmptyTuple
18+
19+
val foo: Foo = mirrorFoo.fromProduct(fooShapedTuple)
20+
val bar: Bar = mirrorBar.fromProduct(barShapedTuple)
21+
val qux: Qux.type = mirrorQux.fromProduct(quxShapedTuple)
22+
23+
assert(foo == Foo(23, 47, "ok"))
24+
assert(bar == Bar(57, 71))
25+
assert(qux == Qux)
26+
27+
val fooOrd = mirrorParent.ordinal(foo)
28+
val barOrd = mirrorParent.ordinal(bar)
29+
val quxOrd = mirrorParent.ordinal(qux)
30+
31+
assert(fooOrd == 0)
32+
assert(barOrd == 1)
33+
assert(quxOrd == 2)
34+
}
35+
36+
@main def Test =
37+
ClassWrapper().testcase()

0 commit comments

Comments
 (0)