From 9cb0649293fd0037828667a166b7e3949ef6bea9 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 19 May 2022 11:35:25 +0200 Subject: [PATCH 1/3] recursively check for product ctor accessibility --- .../dotty/tools/dotc/transform/SymUtils.scala | 38 ++++++++---- .../dotc/transform/SyntheticMembers.scala | 4 +- .../dotty/tools/dotc/typer/Synthesizer.scala | 60 +++++-------------- tests/neg/prod-mirror-inacessible-ctor.scala | 26 ++++++++ .../prod-mirror-inacessible-ctor/Lib_1.scala | 15 +++++ .../prod-mirror-inacessible-ctor/Test_2.scala | 6 ++ 6 files changed, 93 insertions(+), 56 deletions(-) create mode 100644 tests/neg/prod-mirror-inacessible-ctor.scala create mode 100644 tests/run/prod-mirror-inacessible-ctor/Lib_1.scala create mode 100644 tests/run/prod-mirror-inacessible-ctor/Test_2.scala diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 32d1440ac3cb..7f6a8d6d10dd 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -82,11 +82,26 @@ object SymUtils: * parameter section. */ def whyNotGenericProduct(using Context): String = + /** for a case class, if it will have an anonymous mirror, + * check that its constructor can be accessed + * from the calling scope. + */ + def canAccessCtor: Boolean = + def isAccessible(sym: Symbol): Boolean = ctx.owner.isContainedIn(sym) + def isSub(sym: Symbol): Boolean = ctx.owner.ownersIterator.exists(_.derivesFrom(sym)) + val ctor = self.primaryConstructor + (!ctor.isOneOf(Private | Protected) || isSub(self)) // we cant access the ctor because we do not extend cls + && (!ctor.privateWithin.exists || isAccessible(ctor.privateWithin)) // check scope is compatible + + + val companionMirror = self.useCompanionAsProductMirror if (!self.is(CaseClass)) "it is not a case class" else if (self.is(Abstract)) "it is an abstract class" else if (self.primaryConstructor.info.paramInfoss.length != 1) "it takes more than one parameter list" else if (isDerivedValueClass(self)) "it is a value class" + else if (!(companionMirror || canAccessCtor)) s"the constructor of $self is innaccessible from the calling scope." else "" + end whyNotGenericProduct def isGenericProduct(using Context): Boolean = whyNotGenericProduct.isEmpty @@ -120,6 +135,9 @@ object SymUtils: self.isOneOf(FinalOrInline, butNot = Mutable) && (!self.is(Method) || self.is(Accessor)) + def useCompanionAsProductMirror(using Context): Boolean = + self.linkedClass.exists && !self.is(Scala2x) && !self.linkedClass.is(Case) + def useCompanionAsSumMirror(using Context): Boolean = def companionExtendsSum(using Context): Boolean = self.linkedClass.isSubClass(defn.Mirror_SumClass) @@ -145,7 +163,7 @@ object SymUtils: * and also the location of the generated mirror. * - all of its children are generic products, singletons, or generic sums themselves. */ - def whyNotGenericSum(declScope: Symbol)(using Context): String = + def whyNotGenericSum(using Context): String = if (!self.is(Sealed)) s"it is not a sealed ${self.kindString}" else if (!self.isOneOf(AbstractOrTrait)) @@ -153,31 +171,31 @@ object SymUtils: else { val children = self.children val companionMirror = self.useCompanionAsSumMirror - assert(!(companionMirror && (declScope ne self.linkedClass))) def problem(child: Symbol) = { def isAccessible(sym: Symbol): Boolean = - (self.isContainedIn(sym) && (companionMirror || declScope.isContainedIn(sym))) + (self.isContainedIn(sym) && (companionMirror || ctx.owner.isContainedIn(sym))) || sym.is(Module) && isAccessible(sym.owner) if (child == self) "it has anonymous or inaccessible subclasses" else if (!isAccessible(child.owner)) i"its child $child is not accessible" - else if (!child.isClass) "" + else if (!child.isClass) "" // its a singleton enum value else { val s = child.whyNotGenericProduct - if (s.isEmpty) s - else if (child.is(Sealed)) { - val s = child.whyNotGenericSum(if child.useCompanionAsSumMirror then child.linkedClass else ctx.owner) - if (s.isEmpty) s + if s.isEmpty then s + else if child.is(Sealed) then + val s = child.whyNotGenericSum + if s.isEmpty then s else i"its child $child is not a generic sum because $s" - } else i"its child $child is not a generic product because $s" + else + i"its child $child is not a generic product because $s" } } if (children.isEmpty) "it does not have subclasses" else children.map(problem).find(!_.isEmpty).getOrElse("") } - def isGenericSum(declScope: Symbol)(using Context): Boolean = whyNotGenericSum(declScope).isEmpty + def isGenericSum(using Context): Boolean = whyNotGenericSum.isEmpty /** If this is a constructor, its owner: otherwise this. */ final def skipConstructor(using Context): Symbol = diff --git a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala index 32256e43d242..faccb79f3c9a 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -594,9 +594,9 @@ class SyntheticMembers(thisPhase: DenotTransformer) { if (clazz.is(Module)) { if (clazz.is(Case)) makeSingletonMirror() else if (linked.isGenericProduct) makeProductMirror(linked) - else if (linked.isGenericSum(clazz)) makeSumMirror(linked) + else if (linked.isGenericSum) makeSumMirror(linked) else if (linked.is(Sealed)) - derive.println(i"$linked is not a sum because ${linked.whyNotGenericSum(clazz)}") + derive.println(i"$linked is not a sum because ${linked.whyNotGenericSum}") } else if (impl.removeAttachment(ExtendsSingletonMirror).isDefined) makeSingletonMirror() diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index e8a993abd0b6..4da49f767485 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -26,7 +26,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): /** Handlers to synthesize implicits for special types */ type SpecialHandler = (Type, Span) => Context ?=> TreeWithErrors private type SpecialHandlers = List[(ClassSymbol, SpecialHandler)] - + val synthesizedClassTag: SpecialHandler = (formal, span) => formal.argInfos match case arg :: Nil => @@ -285,22 +285,6 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): case OrType(tp1, tp2) => acceptable(tp1, cls) && acceptable(tp2, cls) case _ => tp.classSymbol eq cls - /** for a case class, if it will have an anonymous mirror, - * check that its constructor can be accessed - * from the calling scope. - */ - def canAccessCtor(cls: Symbol): Boolean = - !genAnonyousMirror(cls) || { - def isAccessible(sym: Symbol): Boolean = ctx.owner.isContainedIn(sym) - def isSub(sym: Symbol): Boolean = ctx.owner.ownersIterator.exists(_.derivesFrom(sym)) - val ctor = cls.primaryConstructor - (!ctor.isOneOf(Private | Protected) || isSub(cls)) // we cant access the ctor because we do not extend cls - && (!ctor.privateWithin.exists || isAccessible(ctor.privateWithin)) // check scope is compatible - } - - def genAnonyousMirror(cls: Symbol): Boolean = - cls.is(Scala2x) || cls.linkedClass.is(Case) - def makeProductMirror(cls: Symbol): TreeWithErrors = val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal)) val elemLabels = accessors.map(acc => ConstantType(Constant(acc.name.toString))) @@ -318,21 +302,11 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): .refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType)) .refinedWith(tpnme.MirroredElemLabels, TypeAlias(elemsLabels)) val mirrorRef = - if (genAnonyousMirror(cls)) anonymousMirror(monoType, ExtendsProductMirror, span) - else companionPath(mirroredType, span) + if cls.useCompanionAsProductMirror then companionPath(mirroredType, span) + else anonymousMirror(monoType, ExtendsProductMirror, span) withNoErrors(mirrorRef.cast(mirrorType)) end makeProductMirror - def getError(cls: Symbol): String = - val reason = if !cls.isGenericProduct then - i"because ${cls.whyNotGenericProduct}" - else if !canAccessCtor(cls) then - i"because the constructor of $cls is innaccessible from the calling scope." - else - "" - i"$cls is not a generic product $reason" - end getError - mirroredType match case AndType(tp1, tp2) => orElse(productMirror(tp1, formal, span), productMirror(tp2, formal, span)) @@ -349,21 +323,19 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): withNoErrors(modulePath.cast(mirrorType)) else val cls = mirroredType.classSymbol - if acceptable(mirroredType, cls) - && cls.isGenericProduct - && canAccessCtor(cls) - then + val clsIsGenericProduct = cls.isGenericProduct + if acceptable(mirroredType, cls) && clsIsGenericProduct then makeProductMirror(cls) + else if !clsIsGenericProduct then + (EmptyTree, List(i"$cls is not a generic product because ${cls.whyNotGenericProduct}")) else - (EmptyTree, List(getError(cls))) + EmptyTreeNoError end productMirror private def sumMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors = val cls = mirroredType.classSymbol - val useCompanion = cls.useCompanionAsSumMirror - val declScope = if useCompanion then cls.linkedClass else ctx.owner - val clsIsGenericSum = cls.isGenericSum(declScope) + val clsIsGenericSum = cls.isGenericSum def acceptable(tp: Type): Boolean = tp match case tp: TermRef => false @@ -423,12 +395,12 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): .refinedWith(tpnme.MirroredElemTypes, TypeAlias(elemsType)) .refinedWith(tpnme.MirroredElemLabels, TypeAlias(TypeOps.nestedPairs(elemLabels))) val mirrorRef = - if useCompanion then companionPath(mirroredType, span) + if cls.useCompanionAsSumMirror then companionPath(mirroredType, span) else anonymousMirror(monoType, ExtendsSumMirror, span) withNoErrors(mirrorRef.cast(mirrorType)) else if !clsIsGenericSum then - (EmptyTree, List(i"$cls is not a generic sum because ${cls.whyNotGenericSum(declScope)}")) - else + (EmptyTree, List(i"$cls is not a generic sum because ${cls.whyNotGenericSum}")) + else EmptyTreeNoError end sumMirror @@ -595,7 +567,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): tp.baseType(cls) val base = baseWithRefinements(formal) val result = - if (base <:< formal.widenExpr) + if (base <:< formal.widenExpr) // With the subtype test we enforce that the searched type `formal` is of the right form handler(base, span) else EmptyTreeNoError @@ -609,19 +581,19 @@ end Synthesizer object Synthesizer: - /** Tuple used to store the synthesis result with a list of errors. */ + /** Tuple used to store the synthesis result with a list of errors. */ type TreeWithErrors = (Tree, List[String]) private def withNoErrors(tree: Tree): TreeWithErrors = (tree, List.empty) private val EmptyTreeNoError: TreeWithErrors = withNoErrors(EmptyTree) private def orElse(treeWithErrors1: TreeWithErrors, treeWithErrors2: => TreeWithErrors): TreeWithErrors = treeWithErrors1 match - case (tree, errors) if tree eq genericEmptyTree => + case (tree, errors) if tree eq genericEmptyTree => val (tree2, errors2) = treeWithErrors2 (tree2, errors ::: errors2) case _ => treeWithErrors1 - private def clearErrorsIfNotEmpty(treeWithErrors: TreeWithErrors) = treeWithErrors match + private def clearErrorsIfNotEmpty(treeWithErrors: TreeWithErrors) = treeWithErrors match case (tree, _) if tree eq genericEmptyTree => treeWithErrors case (tree, _) => withNoErrors(tree) diff --git a/tests/neg/prod-mirror-inacessible-ctor.scala b/tests/neg/prod-mirror-inacessible-ctor.scala new file mode 100644 index 000000000000..8da30659f4ff --- /dev/null +++ b/tests/neg/prod-mirror-inacessible-ctor.scala @@ -0,0 +1,26 @@ +import scala.deriving.Mirror + +package lib { + sealed trait Foo + object Foo // normally, would cache a mirror if one exists. + case class Bar private[lib] () extends Foo + case object Bar // force mirror for Bar to be anonymous. + + + object CallSiteSucceed { + val mFoo = summon[Mirror.SumOf[lib.Foo]] // ok + val mBar = summon[Mirror.ProductOf[lib.Bar]] // ok + } + +} + +package app { + + object MustFail { + // we are outsite of accessible scope for Bar's ctor, so this should fail. + + val mFoo = summon[Mirror.SumOf[lib.Foo]] // error + val mBar = summon[Mirror.ProductOf[lib.Bar]] // error + } + +} diff --git a/tests/run/prod-mirror-inacessible-ctor/Lib_1.scala b/tests/run/prod-mirror-inacessible-ctor/Lib_1.scala new file mode 100644 index 000000000000..6f2cc7b794fb --- /dev/null +++ b/tests/run/prod-mirror-inacessible-ctor/Lib_1.scala @@ -0,0 +1,15 @@ +package lib + +import scala.deriving.Mirror + +sealed trait Foo +object Foo // normally, would cache a mirror if one exists. +case class Bar private[lib] () extends Foo +case object Bar // force mirror for Bar to be anonymous. + + +object CallSiteSucceed { + val mFoo = summon[Mirror.SumOf[Foo]] // ok + val mBar = summon[Mirror.ProductOf[Bar]] // ok + val sampleBar = Bar() +} diff --git a/tests/run/prod-mirror-inacessible-ctor/Test_2.scala b/tests/run/prod-mirror-inacessible-ctor/Test_2.scala new file mode 100644 index 000000000000..ed70c56125e2 --- /dev/null +++ b/tests/run/prod-mirror-inacessible-ctor/Test_2.scala @@ -0,0 +1,6 @@ +@main def Test = + assert(lib.CallSiteSucceed.mFoo eq lib.Foo) // binary compatibility with 3.1 + assert(lib.CallSiteSucceed.mBar ne lib.Bar) // anonymous mirror + + assert(lib.CallSiteSucceed.mFoo.ordinal(lib.CallSiteSucceed.sampleBar) == 0) + assert(lib.CallSiteSucceed.mBar.fromProduct(EmptyTuple) == lib.CallSiteSucceed.sampleBar) From 66187b1512d20efab4b8a6fe11cb319c2b1dfcbc Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 19 May 2022 14:32:53 +0200 Subject: [PATCH 2/3] fix error messages --- .../dotty/tools/dotc/typer/Synthesizer.scala | 53 +++++++++++-------- tests/neg/i14025.check | 4 +- tests/neg/i14823.check | 4 +- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 4da49f767485..98e64e96f02e 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -278,12 +278,15 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): private def productMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors = - /** do all parts match the class symbol? */ - def acceptable(tp: Type, cls: Symbol): Boolean = tp match - case tp: HKTypeLambda if tp.resultType.isInstanceOf[HKTypeLambda] => false - case tp: TypeProxy => acceptable(tp.underlying, cls) - case OrType(tp1, tp2) => acceptable(tp1, cls) && acceptable(tp2, cls) - case _ => tp.classSymbol eq cls + def whyNotAcceptableType(tp: Type, cls: Symbol): String = tp match + 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) => + Seq(tp1, tp2).map(whyNotAcceptableType(_, cls)).find(_.nonEmpty).getOrElse("") + case _ => + if tp.classSymbol eq cls then "" + else i"a subpart reduces to the more precise ${tp.classSymbol}, expected $cls" def makeProductMirror(cls: Symbol): TreeWithErrors = val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal)) @@ -323,13 +326,11 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): withNoErrors(modulePath.cast(mirrorType)) else val cls = mirroredType.classSymbol - val clsIsGenericProduct = cls.isGenericProduct - if acceptable(mirroredType, cls) && clsIsGenericProduct then - makeProductMirror(cls) - else if !clsIsGenericProduct then - (EmptyTree, List(i"$cls is not a generic product because ${cls.whyNotGenericProduct}")) - else - EmptyTreeNoError + 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") end productMirror private def sumMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors = @@ -337,14 +338,21 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val cls = mirroredType.classSymbol val clsIsGenericSum = cls.isGenericSum - def acceptable(tp: Type): Boolean = tp match - case tp: TermRef => false - case tp: HKTypeLambda if tp.resultType.isInstanceOf[HKTypeLambda] => false - case tp: TypeProxy => acceptable(tp.underlying) - case OrType(tp1, tp2) => acceptable(tp1) && acceptable(tp2) - case _ => tp.classSymbol eq cls + 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) => + Seq(tp1, tp2).map(whyNotAcceptableType).find(_.nonEmpty).getOrElse("") + case _ => + if tp.classSymbol eq cls then "" + else i"a subpart reduces to the more precise ${tp.classSymbol}, expected $cls" + + + val acceptableMsg = whyNotAcceptableType(mirroredType) - if acceptable(mirroredType) && clsIsGenericSum then + if acceptableMsg.isEmpty && clsIsGenericSum then val elemLabels = cls.children.map(c => ConstantType(Constant(c.name.toString))) def solve(sym: Symbol): Type = sym match @@ -398,8 +406,10 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): if cls.useCompanionAsSumMirror then companionPath(mirroredType, span) else anonymousMirror(monoType, ExtendsSumMirror, span) withNoErrors(mirrorRef.cast(mirrorType)) + else if acceptableMsg.nonEmpty then + withErrors(i"type $mirroredType is not a generic sum because $acceptableMsg") else if !clsIsGenericSum then - (EmptyTree, List(i"$cls is not a generic sum because ${cls.whyNotGenericSum}")) + withErrors(i"$cls is not a generic sum because ${cls.whyNotGenericSum}") else EmptyTreeNoError end sumMirror @@ -584,6 +594,7 @@ object Synthesizer: /** Tuple used to store the synthesis result with a list of errors. */ type TreeWithErrors = (Tree, List[String]) private def withNoErrors(tree: Tree): TreeWithErrors = (tree, List.empty) + private def withErrors(errors: String*): TreeWithErrors = (EmptyTree, errors.toList) private val EmptyTreeNoError: TreeWithErrors = withNoErrors(EmptyTree) diff --git a/tests/neg/i14025.check b/tests/neg/i14025.check index d86198a96d85..bd29136e3ab0 100644 --- a/tests/neg/i14025.check +++ b/tests/neg/i14025.check @@ -1,8 +1,8 @@ -- Error: tests/neg/i14025.scala:1:88 ---------------------------------------------------------------------------------- 1 |val foo = summon[deriving.Mirror.Product { type MirroredType = [X] =>> [Y] =>> (X, Y) }] // error | ^ - |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 + |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 `* -> *`) -- Error: tests/neg/i14025.scala:2:90 ---------------------------------------------------------------------------------- 2 |val bar = summon[deriving.Mirror.Sum { type MirroredType = [X] =>> [Y] =>> List[(X, Y)] }] // error | ^ - |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 + |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 `* -> *`) diff --git a/tests/neg/i14823.check b/tests/neg/i14823.check index 6b658644724c..1da3c0560d9c 100644 --- a/tests/neg/i14823.check +++ b/tests/neg/i14823.check @@ -2,5 +2,5 @@ 8 |val baz = summon[Mirror.Of[SubA[Int] | SubB[Int]]] // error | ^ |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]]: - | * class Cov is not a generic product - | * class Cov is not a generic sum because it is not a sealed class + | * type SubA[Int] | SubB[Int] is not a generic product because a subpart reduces to the more precise class SubA, expected class Cov + | * type SubA[Int] | SubB[Int] is not a generic sum because a subpart reduces to the more precise class SubA, expected class Cov From f1b95a5110425cc7d990a78459bd4a9e78bdba27 Mon Sep 17 00:00:00 2001 From: Jamie Thompson Date: Thu, 19 May 2022 15:18:13 +0200 Subject: [PATCH 3/3] handle widened module class in productMirror --- .../dotty/tools/dotc/transform/SymUtils.scala | 2 +- .../dotty/tools/dotc/typer/Synthesizer.scala | 27 ++++++++++++------- tests/run/i15234.scala | 22 +++++++++++++++ 3 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 tests/run/i15234.scala diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 7f6a8d6d10dd..43790545c1fa 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -94,7 +94,7 @@ object SymUtils: && (!ctor.privateWithin.exists || isAccessible(ctor.privateWithin)) // check scope is compatible - val companionMirror = self.useCompanionAsProductMirror + def companionMirror = self.useCompanionAsProductMirror if (!self.is(CaseClass)) "it is not a case class" else if (self.is(Abstract)) "it is an abstract class" else if (self.primaryConstructor.info.paramInfoss.length != 1) "it takes more than one parameter list" diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index 98e64e96f02e..36addf639932 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -310,22 +310,31 @@ 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 _ => - if mirroredType.termSymbol.is(CaseVal) then - val module = mirroredType.termSymbol - val modulePath = pathFor(mirroredType).withSpan(span) - if module.info.classSymbol.is(Scala2x) then - val mirrorType = mirrorCore(defn.Mirror_SingletonProxyClass, mirroredType, mirroredType, module.name, formal) - val mirrorRef = New(defn.Mirror_SingletonProxyClass.typeRef, modulePath :: Nil) + 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) + val mirrorRef = New(defn.Mirror_SingletonProxyClass.typeRef, singletonPath :: Nil) withNoErrors(mirrorRef.cast(mirrorType)) else - val mirrorType = mirrorCore(defn.Mirror_SingletonClass, mirroredType, mirroredType, module.name, formal) - withNoErrors(modulePath.cast(mirrorType)) + val mirrorType = mirrorCore(defn.Mirror_SingletonClass, mirroredType, mirroredType, singleton.name, formal) + withNoErrors(singletonPath.cast(mirrorType)) else - val cls = mirroredType.classSymbol val acceptableMsg = whyNotAcceptableType(mirroredType, cls) if acceptableMsg.isEmpty then if cls.isGenericProduct then makeProductMirror(cls) diff --git a/tests/run/i15234.scala b/tests/run/i15234.scala new file mode 100644 index 000000000000..28495e9d011e --- /dev/null +++ b/tests/run/i15234.scala @@ -0,0 +1,22 @@ +import scala.deriving.Mirror + +package lib { + enum Foo: + case A + + case object Bar +} + +package app { + object Foo: + val A: lib.Foo.A.type = lib.Foo.A + val Bar: lib.Bar.type = lib.Bar +} + + +@main def Test = + assert(summon[Mirror.Of[scala.Nil.type]].fromProduct(EmptyTuple) == Nil) // alias scala 2 defined + assert(summon[Mirror.Of[lib.Foo.A.type]].fromProduct(EmptyTuple) == lib.Foo.A) // real mirror + assert(summon[Mirror.Of[lib.Bar.type]].fromProduct(EmptyTuple) == lib.Bar) // real mirror + assert(summon[Mirror.Of[app.Foo.A.type]].fromProduct(EmptyTuple) == lib.Foo.A) // alias mirror + assert(summon[Mirror.Of[app.Bar.type]].fromProduct(EmptyTuple) == lib.Bar) // alias mirror