Skip to content

Commit 001bfc3

Browse files
authored
Merge pull request #15231 from dotty-staging/fix-15222
fix #15222 recursively check for product ctor accessibility
2 parents dbbb8b7 + f1b95a5 commit 001bfc3

File tree

9 files changed

+164
-85
lines changed

9 files changed

+164
-85
lines changed

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

+28-10
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,26 @@ object SymUtils:
8282
* parameter section.
8383
*/
8484
def whyNotGenericProduct(using Context): String =
85+
/** for a case class, if it will have an anonymous mirror,
86+
* check that its constructor can be accessed
87+
* from the calling scope.
88+
*/
89+
def canAccessCtor: Boolean =
90+
def isAccessible(sym: Symbol): Boolean = ctx.owner.isContainedIn(sym)
91+
def isSub(sym: Symbol): Boolean = ctx.owner.ownersIterator.exists(_.derivesFrom(sym))
92+
val ctor = self.primaryConstructor
93+
(!ctor.isOneOf(Private | Protected) || isSub(self)) // we cant access the ctor because we do not extend cls
94+
&& (!ctor.privateWithin.exists || isAccessible(ctor.privateWithin)) // check scope is compatible
95+
96+
97+
def companionMirror = self.useCompanionAsProductMirror
8598
if (!self.is(CaseClass)) "it is not a case class"
8699
else if (self.is(Abstract)) "it is an abstract class"
87100
else if (self.primaryConstructor.info.paramInfoss.length != 1) "it takes more than one parameter list"
88101
else if (isDerivedValueClass(self)) "it is a value class"
102+
else if (!(companionMirror || canAccessCtor)) s"the constructor of $self is innaccessible from the calling scope."
89103
else ""
104+
end whyNotGenericProduct
90105

91106
def isGenericProduct(using Context): Boolean = whyNotGenericProduct.isEmpty
92107

@@ -120,6 +135,9 @@ object SymUtils:
120135
self.isOneOf(FinalOrInline, butNot = Mutable)
121136
&& (!self.is(Method) || self.is(Accessor))
122137

138+
def useCompanionAsProductMirror(using Context): Boolean =
139+
self.linkedClass.exists && !self.is(Scala2x) && !self.linkedClass.is(Case)
140+
123141
def useCompanionAsSumMirror(using Context): Boolean =
124142
def companionExtendsSum(using Context): Boolean =
125143
self.linkedClass.isSubClass(defn.Mirror_SumClass)
@@ -145,39 +163,39 @@ object SymUtils:
145163
* and also the location of the generated mirror.
146164
* - all of its children are generic products, singletons, or generic sums themselves.
147165
*/
148-
def whyNotGenericSum(declScope: Symbol)(using Context): String =
166+
def whyNotGenericSum(using Context): String =
149167
if (!self.is(Sealed))
150168
s"it is not a sealed ${self.kindString}"
151169
else if (!self.isOneOf(AbstractOrTrait))
152170
"it is not an abstract class"
153171
else {
154172
val children = self.children
155173
val companionMirror = self.useCompanionAsSumMirror
156-
assert(!(companionMirror && (declScope ne self.linkedClass)))
157174
def problem(child: Symbol) = {
158175

159176
def isAccessible(sym: Symbol): Boolean =
160-
(self.isContainedIn(sym) && (companionMirror || declScope.isContainedIn(sym)))
177+
(self.isContainedIn(sym) && (companionMirror || ctx.owner.isContainedIn(sym)))
161178
|| sym.is(Module) && isAccessible(sym.owner)
162179

163180
if (child == self) "it has anonymous or inaccessible subclasses"
164181
else if (!isAccessible(child.owner)) i"its child $child is not accessible"
165-
else if (!child.isClass) ""
182+
else if (!child.isClass) "" // its a singleton enum value
166183
else {
167184
val s = child.whyNotGenericProduct
168-
if (s.isEmpty) s
169-
else if (child.is(Sealed)) {
170-
val s = child.whyNotGenericSum(if child.useCompanionAsSumMirror then child.linkedClass else ctx.owner)
171-
if (s.isEmpty) s
185+
if s.isEmpty then s
186+
else if child.is(Sealed) then
187+
val s = child.whyNotGenericSum
188+
if s.isEmpty then s
172189
else i"its child $child is not a generic sum because $s"
173-
} else i"its child $child is not a generic product because $s"
190+
else
191+
i"its child $child is not a generic product because $s"
174192
}
175193
}
176194
if (children.isEmpty) "it does not have subclasses"
177195
else children.map(problem).find(!_.isEmpty).getOrElse("")
178196
}
179197

180-
def isGenericSum(declScope: Symbol)(using Context): Boolean = whyNotGenericSum(declScope).isEmpty
198+
def isGenericSum(using Context): Boolean = whyNotGenericSum.isEmpty
181199

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

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -594,9 +594,9 @@ class SyntheticMembers(thisPhase: DenotTransformer) {
594594
if (clazz.is(Module)) {
595595
if (clazz.is(Case)) makeSingletonMirror()
596596
else if (linked.isGenericProduct) makeProductMirror(linked)
597-
else if (linked.isGenericSum(clazz)) makeSumMirror(linked)
597+
else if (linked.isGenericSum) makeSumMirror(linked)
598598
else if (linked.is(Sealed))
599-
derive.println(i"$linked is not a sum because ${linked.whyNotGenericSum(clazz)}")
599+
derive.println(i"$linked is not a sum because ${linked.whyNotGenericSum}")
600600
}
601601
else if (impl.removeAttachment(ExtendsSingletonMirror).isDefined)
602602
makeSingletonMirror()

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

+61-69
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
2626
/** Handlers to synthesize implicits for special types */
2727
type SpecialHandler = (Type, Span) => Context ?=> TreeWithErrors
2828
private type SpecialHandlers = List[(ClassSymbol, SpecialHandler)]
29-
29+
3030
val synthesizedClassTag: SpecialHandler = (formal, span) =>
3131
formal.argInfos match
3232
case arg :: Nil =>
@@ -278,28 +278,15 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
278278

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

281-
/** do all parts match the class symbol? */
282-
def acceptable(tp: Type, cls: Symbol): Boolean = tp match
283-
case tp: HKTypeLambda if tp.resultType.isInstanceOf[HKTypeLambda] => false
284-
case tp: TypeProxy => acceptable(tp.underlying, cls)
285-
case OrType(tp1, tp2) => acceptable(tp1, cls) && acceptable(tp2, cls)
286-
case _ => tp.classSymbol eq cls
287-
288-
/** for a case class, if it will have an anonymous mirror,
289-
* check that its constructor can be accessed
290-
* from the calling scope.
291-
*/
292-
def canAccessCtor(cls: Symbol): Boolean =
293-
!genAnonyousMirror(cls) || {
294-
def isAccessible(sym: Symbol): Boolean = ctx.owner.isContainedIn(sym)
295-
def isSub(sym: Symbol): Boolean = ctx.owner.ownersIterator.exists(_.derivesFrom(sym))
296-
val ctor = cls.primaryConstructor
297-
(!ctor.isOneOf(Private | Protected) || isSub(cls)) // we cant access the ctor because we do not extend cls
298-
&& (!ctor.privateWithin.exists || isAccessible(ctor.privateWithin)) // check scope is compatible
299-
}
300-
301-
def genAnonyousMirror(cls: Symbol): Boolean =
302-
cls.is(Scala2x) || cls.linkedClass.is(Case)
281+
def whyNotAcceptableType(tp: Type, cls: Symbol): String = tp match
282+
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) =>
286+
Seq(tp1, tp2).map(whyNotAcceptableType(_, cls)).find(_.nonEmpty).getOrElse("")
287+
case _ =>
288+
if tp.classSymbol eq cls then ""
289+
else i"a subpart reduces to the more precise ${tp.classSymbol}, expected $cls"
303290

304291
def makeProductMirror(cls: Symbol): TreeWithErrors =
305292
val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal))
@@ -318,61 +305,63 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
318305
.refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType))
319306
.refinedWith(tpnme.MirroredElemLabels, TypeAlias(elemsLabels))
320307
val mirrorRef =
321-
if (genAnonyousMirror(cls)) anonymousMirror(monoType, ExtendsProductMirror, span)
322-
else companionPath(mirroredType, span)
308+
if cls.useCompanionAsProductMirror then companionPath(mirroredType, span)
309+
else anonymousMirror(monoType, ExtendsProductMirror, span)
323310
withNoErrors(mirrorRef.cast(mirrorType))
324311
end makeProductMirror
325312

326-
def getError(cls: Symbol): String =
327-
val reason = if !cls.isGenericProduct then
328-
i"because ${cls.whyNotGenericProduct}"
329-
else if !canAccessCtor(cls) then
330-
i"because the constructor of $cls is innaccessible from the calling scope."
331-
else
332-
""
333-
i"$cls is not a generic product $reason"
334-
end getError
313+
/** widen TermRef to see if they are an alias to an enum singleton */
314+
def isEnumSingletonRef(tp: Type)(using Context): Boolean = tp match
315+
case tp: TermRef =>
316+
val sym = tp.termSymbol
317+
sym.isEnumCase || (!tp.isOverloaded && isEnumSingletonRef(tp.underlying.widenExpr))
318+
case _ => false
335319

336320
mirroredType match
337321
case AndType(tp1, tp2) =>
338322
orElse(productMirror(tp1, formal, span), productMirror(tp2, formal, span))
339323
case _ =>
340-
if mirroredType.termSymbol.is(CaseVal) then
341-
val module = mirroredType.termSymbol
342-
val modulePath = pathFor(mirroredType).withSpan(span)
343-
if module.info.classSymbol.is(Scala2x) then
344-
val mirrorType = mirrorCore(defn.Mirror_SingletonProxyClass, mirroredType, mirroredType, module.name, formal)
345-
val mirrorRef = New(defn.Mirror_SingletonProxyClass.typeRef, modulePath :: Nil)
324+
val cls = mirroredType.classSymbol
325+
if isEnumSingletonRef(mirroredType) || cls.isAllOf(Case | Module) then
326+
val (singleton, singletonRef) =
327+
if mirroredType.termSymbol.exists then (mirroredType.termSymbol, mirroredType)
328+
else (cls.sourceModule, cls.sourceModule.reachableTermRef)
329+
val singletonPath = pathFor(singletonRef).withSpan(span)
330+
if singleton.info.classSymbol.is(Scala2x) then // could be Scala 3 alias of Scala 2 case object.
331+
val mirrorType = mirrorCore(defn.Mirror_SingletonProxyClass, mirroredType, mirroredType, singleton.name, formal)
332+
val mirrorRef = New(defn.Mirror_SingletonProxyClass.typeRef, singletonPath :: Nil)
346333
withNoErrors(mirrorRef.cast(mirrorType))
347334
else
348-
val mirrorType = mirrorCore(defn.Mirror_SingletonClass, mirroredType, mirroredType, module.name, formal)
349-
withNoErrors(modulePath.cast(mirrorType))
335+
val mirrorType = mirrorCore(defn.Mirror_SingletonClass, mirroredType, mirroredType, singleton.name, formal)
336+
withNoErrors(singletonPath.cast(mirrorType))
350337
else
351-
val cls = mirroredType.classSymbol
352-
if acceptable(mirroredType, cls)
353-
&& cls.isGenericProduct
354-
&& canAccessCtor(cls)
355-
then
356-
makeProductMirror(cls)
357-
else
358-
(EmptyTree, List(getError(cls)))
338+
val acceptableMsg = whyNotAcceptableType(mirroredType, cls)
339+
if acceptableMsg.isEmpty then
340+
if cls.isGenericProduct then makeProductMirror(cls)
341+
else withErrors(i"$cls is not a generic product because ${cls.whyNotGenericProduct}")
342+
else withErrors(i"type $mirroredType is not a generic product because $acceptableMsg")
359343
end productMirror
360344

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

363347
val cls = mirroredType.classSymbol
364-
val useCompanion = cls.useCompanionAsSumMirror
365-
val declScope = if useCompanion then cls.linkedClass else ctx.owner
366-
val clsIsGenericSum = cls.isGenericSum(declScope)
367-
368-
def acceptable(tp: Type): Boolean = tp match
369-
case tp: TermRef => false
370-
case tp: HKTypeLambda if tp.resultType.isInstanceOf[HKTypeLambda] => false
371-
case tp: TypeProxy => acceptable(tp.underlying)
372-
case OrType(tp1, tp2) => acceptable(tp1) && acceptable(tp2)
373-
case _ => tp.classSymbol eq cls
374-
375-
if acceptable(mirroredType) && clsIsGenericSum then
348+
val clsIsGenericSum = cls.isGenericSum
349+
350+
def whyNotAcceptableType(tp: Type): String = tp match
351+
case tp: TermRef => i"its subpart $tp is a term reference"
352+
case tp: HKTypeLambda if tp.resultType.isInstanceOf[HKTypeLambda] =>
353+
i"its subpart $tp is not a supported kind (either `*` or `* -> *`)"
354+
case tp: TypeProxy => whyNotAcceptableType(tp.underlying)
355+
case OrType(tp1, tp2) =>
356+
Seq(tp1, tp2).map(whyNotAcceptableType).find(_.nonEmpty).getOrElse("")
357+
case _ =>
358+
if tp.classSymbol eq cls then ""
359+
else i"a subpart reduces to the more precise ${tp.classSymbol}, expected $cls"
360+
361+
362+
val acceptableMsg = whyNotAcceptableType(mirroredType)
363+
364+
if acceptableMsg.isEmpty && clsIsGenericSum then
376365
val elemLabels = cls.children.map(c => ConstantType(Constant(c.name.toString)))
377366

378367
def solve(sym: Symbol): Type = sym match
@@ -423,12 +412,14 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
423412
.refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType))
424413
.refinedWith(tpnme.MirroredElemLabels, TypeAlias(TypeOps.nestedPairs(elemLabels)))
425414
val mirrorRef =
426-
if useCompanion then companionPath(mirroredType, span)
415+
if cls.useCompanionAsSumMirror then companionPath(mirroredType, span)
427416
else anonymousMirror(monoType, ExtendsSumMirror, span)
428417
withNoErrors(mirrorRef.cast(mirrorType))
418+
else if acceptableMsg.nonEmpty then
419+
withErrors(i"type $mirroredType is not a generic sum because $acceptableMsg")
429420
else if !clsIsGenericSum then
430-
(EmptyTree, List(i"$cls is not a generic sum because ${cls.whyNotGenericSum(declScope)}"))
431-
else
421+
withErrors(i"$cls is not a generic sum because ${cls.whyNotGenericSum}")
422+
else
432423
EmptyTreeNoError
433424
end sumMirror
434425

@@ -595,7 +586,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context):
595586
tp.baseType(cls)
596587
val base = baseWithRefinements(formal)
597588
val result =
598-
if (base <:< formal.widenExpr)
589+
if (base <:< formal.widenExpr)
599590
// With the subtype test we enforce that the searched type `formal` is of the right form
600591
handler(base, span)
601592
else EmptyTreeNoError
@@ -609,19 +600,20 @@ end Synthesizer
609600

610601
object Synthesizer:
611602

612-
/** Tuple used to store the synthesis result with a list of errors. */
603+
/** Tuple used to store the synthesis result with a list of errors. */
613604
type TreeWithErrors = (Tree, List[String])
614605
private def withNoErrors(tree: Tree): TreeWithErrors = (tree, List.empty)
606+
private def withErrors(errors: String*): TreeWithErrors = (EmptyTree, errors.toList)
615607

616608
private val EmptyTreeNoError: TreeWithErrors = withNoErrors(EmptyTree)
617609

618610
private def orElse(treeWithErrors1: TreeWithErrors, treeWithErrors2: => TreeWithErrors): TreeWithErrors = treeWithErrors1 match
619-
case (tree, errors) if tree eq genericEmptyTree =>
611+
case (tree, errors) if tree eq genericEmptyTree =>
620612
val (tree2, errors2) = treeWithErrors2
621613
(tree2, errors ::: errors2)
622614
case _ => treeWithErrors1
623615

624-
private def clearErrorsIfNotEmpty(treeWithErrors: TreeWithErrors) = treeWithErrors match
616+
private def clearErrorsIfNotEmpty(treeWithErrors: TreeWithErrors) = treeWithErrors match
625617
case (tree, _) if tree eq genericEmptyTree => treeWithErrors
626618
case (tree, _) => withNoErrors(tree)
627619

tests/neg/i14025.check

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
-- Error: tests/neg/i14025.scala:1:88 ----------------------------------------------------------------------------------
22
1 |val foo = summon[deriving.Mirror.Product { type MirroredType = [X] =>> [Y] =>> (X, Y) }] // error
33
| ^
4-
|No given instance of type deriving.Mirror.Product{MirroredType[X] = [Y] =>> (X, Y)} was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Product{MirroredType[X] = [Y] =>> (X, Y)}: class Tuple2 is not a generic product
4+
|No given instance of type deriving.Mirror.Product{MirroredType[X] = [Y] =>> (X, Y)} was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Product{MirroredType[X] = [Y] =>> (X, Y)}: type [X] =>> [Y] =>> (X, Y) is not a generic product because its subpart [X] =>> [Y] =>> (X, Y) is not a supported kind (either `*` or `* -> *`)
55
-- Error: tests/neg/i14025.scala:2:90 ----------------------------------------------------------------------------------
66
2 |val bar = summon[deriving.Mirror.Sum { type MirroredType = [X] =>> [Y] =>> List[(X, Y)] }] // error
77
| ^
8-
|No given instance of type deriving.Mirror.Sum{MirroredType[X] = [Y] =>> List[(X, Y)]} was found for parameter x of method summon in object Predef
8+
|No given instance of type deriving.Mirror.Sum{MirroredType[X] = [Y] =>> List[(X, Y)]} was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Sum{MirroredType[X] = [Y] =>> List[(X, Y)]}: type [X] =>> [Y] =>> List[(X, Y)] is not a generic sum because its subpart [X] =>> [Y] =>> List[(X, Y)] is not a supported kind (either `*` or `* -> *`)

tests/neg/i14823.check

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
8 |val baz = summon[Mirror.Of[SubA[Int] | SubB[Int]]] // error
33
| ^
44
|No given instance of type deriving.Mirror.Of[SubA[Int] | SubB[Int]] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[SubA[Int] | SubB[Int]]:
5-
| * class Cov is not a generic product
6-
| * class Cov is not a generic sum because it is not a sealed class
5+
| * type SubA[Int] | SubB[Int] is not a generic product because a subpart reduces to the more precise class SubA, expected class Cov
6+
| * type SubA[Int] | SubB[Int] is not a generic sum because a subpart reduces to the more precise class SubA, expected class Cov
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import scala.deriving.Mirror
2+
3+
package lib {
4+
sealed trait Foo
5+
object Foo // normally, would cache a mirror if one exists.
6+
case class Bar private[lib] () extends Foo
7+
case object Bar // force mirror for Bar to be anonymous.
8+
9+
10+
object CallSiteSucceed {
11+
val mFoo = summon[Mirror.SumOf[lib.Foo]] // ok
12+
val mBar = summon[Mirror.ProductOf[lib.Bar]] // ok
13+
}
14+
15+
}
16+
17+
package app {
18+
19+
object MustFail {
20+
// we are outsite of accessible scope for Bar's ctor, so this should fail.
21+
22+
val mFoo = summon[Mirror.SumOf[lib.Foo]] // error
23+
val mBar = summon[Mirror.ProductOf[lib.Bar]] // error
24+
}
25+
26+
}

tests/run/i15234.scala

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import scala.deriving.Mirror
2+
3+
package lib {
4+
enum Foo:
5+
case A
6+
7+
case object Bar
8+
}
9+
10+
package app {
11+
object Foo:
12+
val A: lib.Foo.A.type = lib.Foo.A
13+
val Bar: lib.Bar.type = lib.Bar
14+
}
15+
16+
17+
@main def Test =
18+
assert(summon[Mirror.Of[scala.Nil.type]].fromProduct(EmptyTuple) == Nil) // alias scala 2 defined
19+
assert(summon[Mirror.Of[lib.Foo.A.type]].fromProduct(EmptyTuple) == lib.Foo.A) // real mirror
20+
assert(summon[Mirror.Of[lib.Bar.type]].fromProduct(EmptyTuple) == lib.Bar) // real mirror
21+
assert(summon[Mirror.Of[app.Foo.A.type]].fromProduct(EmptyTuple) == lib.Foo.A) // alias mirror
22+
assert(summon[Mirror.Of[app.Bar.type]].fromProduct(EmptyTuple) == lib.Bar) // alias mirror
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package lib
2+
3+
import scala.deriving.Mirror
4+
5+
sealed trait Foo
6+
object Foo // normally, would cache a mirror if one exists.
7+
case class Bar private[lib] () extends Foo
8+
case object Bar // force mirror for Bar to be anonymous.
9+
10+
11+
object CallSiteSucceed {
12+
val mFoo = summon[Mirror.SumOf[Foo]] // ok
13+
val mBar = summon[Mirror.ProductOf[Bar]] // ok
14+
val sampleBar = Bar()
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
@main def Test =
2+
assert(lib.CallSiteSucceed.mFoo eq lib.Foo) // binary compatibility with 3.1
3+
assert(lib.CallSiteSucceed.mBar ne lib.Bar) // anonymous mirror
4+
5+
assert(lib.CallSiteSucceed.mFoo.ordinal(lib.CallSiteSucceed.sampleBar) == 0)
6+
assert(lib.CallSiteSucceed.mBar.fromProduct(EmptyTuple) == lib.CallSiteSucceed.sampleBar)

0 commit comments

Comments
 (0)