diff --git a/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala b/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala index 709635630254..fba81fec632c 100644 --- a/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/TypeUtils.scala @@ -87,11 +87,11 @@ object TypeUtils { * of this type, while keeping the same prefix. */ def mirrorCompanionRef(using Context): TermRef = self match { - case OrType(tp1, tp2) => - val r1 = tp1.mirrorCompanionRef - val r2 = tp2.mirrorCompanionRef - assert(r1.symbol == r2.symbol, em"mirrorCompanionRef mismatch for $self: $r1, $r2 did not have the same symbol") - r1 + case AndType(tp1, tp2) => + val c1 = tp1.classSymbol + val c2 = tp2.classSymbol + if c1.isSubClass(c2) then tp1.mirrorCompanionRef + else tp2.mirrorCompanionRef // precondition: the parts of the AndType have already been checked to be non-overlapping case self @ TypeRef(prefix, _) if self.symbol.isClass => prefix.select(self.symbol.companionModule).asInstanceOf[TermRef] case self: TypeProxy => diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index baa4ef57acd3..1267000d9733 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -276,16 +276,83 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): case t => mapOver(t) monoMap(mirroredType.resultType) - private def productMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors = + private[Synthesizer] enum MirrorSource: + case ClassSymbol(cls: Symbol) + case Singleton(src: Symbol, tref: TermRef) - def whyNotAcceptableType(tp: Type, cls: Symbol): String = tp match + /** A comparison that chooses the most specific MirrorSource, this is guided by what is necessary for + * `Mirror.Product.fromProduct`. i.e. its result type should be compatible with the erasure of `mirroredType`. + */ + def isSub(that: MirrorSource)(using Context): Boolean = + (this, that) match + case (Singleton(src, _), ClassSymbol(cls)) => src.info.classSymbol.isSubClass(cls) + case (ClassSymbol(cls1), ClassSymbol(cls2)) => cls1.isSubClass(cls2) + case (Singleton(src1, _), Singleton(src2, _)) => src1 eq src2 + case (_: ClassSymbol, _: Singleton) => false + + def show(using Context): String = this match + case ClassSymbol(cls) => i"$cls" + case Singleton(src, _) => i"$src" + + private[Synthesizer] object MirrorSource: + + /** Reduces a mirroredType to either its most specific ClassSymbol, + * or a TermRef to a singleton value. These are + * the base elements required to generate a mirror. + */ + def reduce(mirroredType: Type)(using Context): Either[String, MirrorSource] = mirroredType match + case tp: TypeRef => + val sym = tp.symbol + if sym.isClass then // direct ref to a class, not an alias + if sym.isAllOf(Case | Module) then + // correct widened module ref. Tested in tests/run/i15234.scala + val singleton = sym.sourceModule + Right(MirrorSource.Singleton(singleton, TermRef(tp.prefix, singleton))) + else + Right(MirrorSource.ClassSymbol(sym)) + else + reduce(tp.superType) + case tp: TermRef => + /** Dealias a path type to extract the underlying definition when it is either + * a singleton enum case or a case object. + */ + def reduceToEnumOrCaseObject(tp: Type)(using Context): Symbol = tp match + case tp: TermRef => + val sym = tp.termSymbol + if sym.isEnumCase || (sym.isClass && sym.isAllOf(Case | Module)) then sym + else if sym.exists && !tp.isOverloaded then reduceToEnumOrCaseObject(tp.underlying.widenExpr) + else NoSymbol + case _ => NoSymbol + + // capture enum singleton types. Tested in tests/run/i15234.scala + val singleton = reduceToEnumOrCaseObject(tp) + if singleton.exists then + Right(MirrorSource.Singleton(singleton, tp)) + else + reduce(tp.underlying) 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) => i"its subpart `$tp` is a top-level union type." - case _ => - if tp.classSymbol eq cls then "" - else i"a subpart reduces to the more precise ${tp.classSymbol}, expected $cls" + Left(i"its subpart `$tp` is not a supported kind (either `*` or `* -> *`)") + case tp: TypeProxy => + reduce(tp.underlying) + case tp @ AndType(l, r) => + for + lsrc <- reduce(l) + rsrc <- reduce(r) + res <- locally { + if lsrc.isSub(rsrc) then Right(lsrc) + else if rsrc.isSub(lsrc) then Right(rsrc) + else Left(i"its subpart `$tp` is an intersection of unrelated definitions ${lsrc.show} and ${rsrc.show}.") + } + yield + res + case tp: OrType => + Left(i"its subpart `$tp` is a top-level union type.") + case tp => + Left(i"its subpart `$tp` is an unsupported type.") + + end MirrorSource + + private def productMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors = def makeProductMirror(cls: Symbol): TreeWithErrors = val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal)) @@ -309,55 +376,34 @@ 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 _ => - 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) + MirrorSource.reduce(mirroredType) match + case Right(msrc) => msrc match + case MirrorSource.Singleton(_, tref) => + val singleton = tref.termSymbol // prefer alias name over the orignal name + val singletonPath = pathFor(tref).withSpan(span) + if tref.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, singleton.name, formal) withNoErrors(singletonPath.cast(mirrorType)) - else - 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") + case MirrorSource.ClassSymbol(cls) => + if cls.isGenericProduct then makeProductMirror(cls) + else withErrors(i"$cls is not a generic product because ${cls.whyNotGenericProduct}") + case Left(msg) => + withErrors(i"type `$mirroredType` is not a generic product because $msg") end productMirror private def sumMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors = - val cls = mirroredType.classSymbol - val clsIsGenericSum = cls.isGenericSum + val (acceptableMsg, cls) = MirrorSource.reduce(mirroredType) match + case Right(MirrorSource.Singleton(_, tp)) => (i"its subpart `$tp` is a term reference", NoSymbol) + case Right(MirrorSource.ClassSymbol(cls)) => ("", cls) + case Left(msg) => (msg, NoSymbol) - 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) => i"its subpart `$tp` is a top-level union type." - case _ => - if tp.classSymbol eq cls then "" - else i"a subpart reduces to the more precise ${tp.classSymbol}, expected $cls" - - - val acceptableMsg = whyNotAcceptableType(mirroredType) + val clsIsGenericSum = cls.isGenericSum if acceptableMsg.isEmpty && clsIsGenericSum then val elemLabels = cls.children.map(c => ConstantType(Constant(c.name.toString))) diff --git a/tests/neg/i15190.scala b/tests/neg/i15190.scala new file mode 100644 index 000000000000..8a0bb3aa9e17 --- /dev/null +++ b/tests/neg/i15190.scala @@ -0,0 +1,17 @@ +import scala.deriving.Mirror + +trait Mixin +object Mixin + +trait Parent +object Parent + +sealed trait Fruit extends Parent +object Fruit { + case object Apple extends Fruit + case object Orange extends Fruit +} + +@main def Test = { + summon[Mirror.SumOf[Fruit & Mixin]] // error: not a sum type +} diff --git a/tests/neg/mirror-synthesis-errors-b.check b/tests/neg/mirror-synthesis-errors-b.check new file mode 100644 index 000000000000..ea41d14da296 --- /dev/null +++ b/tests/neg/mirror-synthesis-errors-b.check @@ -0,0 +1,40 @@ +-- Error: tests/neg/mirror-synthesis-errors-b.scala:21:56 -------------------------------------------------------------- +21 |val testA = summon[Mirror.ProductOf[Cns[Int] & Sm[Int]]] // error: unreleated + | ^ + |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. +-- Error: tests/neg/mirror-synthesis-errors-b.scala:22:56 -------------------------------------------------------------- +22 |val testB = summon[Mirror.ProductOf[Sm[Int] & Cns[Int]]] // error: unreleated + | ^ + |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. +-- Error: tests/neg/mirror-synthesis-errors-b.scala:23:49 -------------------------------------------------------------- +23 |val testC = summon[Mirror.Of[Cns[Int] & Sm[Int]]] // error: unreleated + | ^ + |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]]: + | * 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. + | * 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. +-- Error: tests/neg/mirror-synthesis-errors-b.scala:24:49 -------------------------------------------------------------- +24 |val testD = summon[Mirror.Of[Sm[Int] & Cns[Int]]] // error: unreleated + | ^ + |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]]: + | * 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. + | * 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. +-- Error: tests/neg/mirror-synthesis-errors-b.scala:25:55 -------------------------------------------------------------- +25 |val testE = summon[Mirror.ProductOf[Sm[Int] & Nn.type]] // error: unreleated + | ^ + |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. +-- Error: tests/neg/mirror-synthesis-errors-b.scala:26:55 -------------------------------------------------------------- +26 |val testF = summon[Mirror.ProductOf[Nn.type & Sm[Int]]] // error: unreleated + | ^ + |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. +-- Error: tests/neg/mirror-synthesis-errors-b.scala:27:54 -------------------------------------------------------------- +27 |val testG = summon[Mirror.Of[Foo.A.type & Foo.B.type]] // error: unreleated + | ^ + |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)]: + | * 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. + | * 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. +-- Error: tests/neg/mirror-synthesis-errors-b.scala:28:54 -------------------------------------------------------------- +28 |val testH = summon[Mirror.Of[Foo.B.type & Foo.A.type]] // error: unreleated + | ^ + |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)]: + | * 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. + | * 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. diff --git a/tests/neg/mirror-synthesis-errors-b.scala b/tests/neg/mirror-synthesis-errors-b.scala new file mode 100644 index 000000000000..620bd4a8c278 --- /dev/null +++ b/tests/neg/mirror-synthesis-errors-b.scala @@ -0,0 +1,32 @@ +import scala.deriving.Mirror + +sealed trait Lst[+A] // AKA: scala.collection.immutable.List +case class Cns[+A](head: A, tail: Lst[A]) extends Lst[A] +case object Nl extends Lst[Nothing] + +sealed trait Opt[+A] // AKA: scala.Option +case class Sm[+A](value: A) extends Opt[A] +case object Nn extends Opt[Nothing] + +enum Foo: + case A, B + +object Bar: + val A: Foo.A.type = Foo.A // alias of Foo.A + type A = Foo.A.type // type alias + +case object Baz +type Baz = Baz.type + +val testA = summon[Mirror.ProductOf[Cns[Int] & Sm[Int]]] // error: unreleated +val testB = summon[Mirror.ProductOf[Sm[Int] & Cns[Int]]] // error: unreleated +val testC = summon[Mirror.Of[Cns[Int] & Sm[Int]]] // error: unreleated +val testD = summon[Mirror.Of[Sm[Int] & Cns[Int]]] // error: unreleated +val testE = summon[Mirror.ProductOf[Sm[Int] & Nn.type]] // error: unreleated +val testF = summon[Mirror.ProductOf[Nn.type & Sm[Int]]] // error: unreleated +val testG = summon[Mirror.Of[Foo.A.type & Foo.B.type]] // error: unreleated +val testH = summon[Mirror.Of[Foo.B.type & Foo.A.type]] // error: unreleated +val testI = summon[Mirror.Of[Foo.A.type & Bar.A.type]] // ok +val testJ = summon[Mirror.Of[Bar.A.type & Foo.A.type]] // ok +val testK = summon[Mirror.Of[Foo.A.type & Bar.A.type & Bar.A]] // ok +val testL = summon[Mirror.Of[Baz & Baz.type]] // ok diff --git a/tests/run/i15190.scala b/tests/run/i15190.scala new file mode 100644 index 000000000000..09b7a0c1edc8 --- /dev/null +++ b/tests/run/i15190.scala @@ -0,0 +1,19 @@ +import scala.deriving.Mirror + +trait Mixin +object Mixin + +trait Parent +object Parent + +sealed trait Fruit extends Parent +object Fruit { + case object Apple extends Fruit + case object Orange extends Fruit +} + +@main def Test = { + val mFruit = summon[Mirror.SumOf[Fruit & Parent]] + assert(mFruit.ordinal(Fruit.Apple) == 0) + assert(mFruit.ordinal(Fruit.Orange) == 1) +}