diff --git a/compiler/src/dotty/tools/dotc/core/TypeOps.scala b/compiler/src/dotty/tools/dotc/core/TypeOps.scala index 0174fb2f3da9..2ff2570dfa02 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeOps.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeOps.scala @@ -868,4 +868,73 @@ object TypeOps: def stripTypeVars(tp: Type)(using Context): Type = new StripTypeVarsMap().apply(tp) + /** computes a prefix for `child`, derived from its common prefix with `pre` + * - `pre` is assumed to be the prefix of `parent` at a given callsite. + * - `child` is assumed to be the sealed child of `parent`, and reachable according to `whyNotGenericSum`. + */ + def childPrefix(pre: Type, parent: Symbol, child: Symbol)(using Context): Type = + // Example, given this class hierarchy, we can see how this should work + // when summoning a mirror for `wrapper.Color`: + // + // package example + // object Outer3: + // class Wrapper: + // sealed trait Color + // val wrapper = new Wrapper + // object Inner: + // case object Red extends wrapper.Color + // case object Green extends wrapper.Color + // case object Blue extends wrapper.Color + // + // summon[Mirror.SumOf[wrapper.Color]] + // ^^^^^^^^^^^^^ + // > pre = example.Outer3.wrapper.type + // > parent = sealed trait example.Outer3.Wrapper.Color + // > child = module val example.Outer3.Innner.Red + // > parentOwners = [example, Outer3, Wrapper] // computed from definition + // > childOwners = [example, Outer3, Inner] // computed from definition + // > parentRest = [Wrapper] // strip common owners from `childOwners` + // > childRest = [Inner] // strip common owners from `parentOwners` + // > commonPrefix = example.Outer3.type // i.e. parentRest has only 1 element, use 1st subprefix of `pre`. + // > childPrefix = example.Outer3.Inner.type // select all symbols in `childRest` from `commonPrefix` + + /** unwind the prefix into a sequence of sub-prefixes, selecting the one at `limit` + * @return `NoType` if there is an unrecognised prefix type. + */ + def subPrefixAt(pre: Type, limit: Int): Type = + def go(pre: Type, limit: Int): Type = + if limit == 0 then pre // EXIT: No More prefix + else pre match + case pre: ThisType => go(pre.tref.prefix, limit - 1) + case pre: TermRef => go(pre.prefix, limit - 1) + case _:SuperType | NoPrefix => pre.ensuring(limit == 1) // EXIT: can't rewind further than this + case _ => NoType // EXIT: unrecognized prefix + go(pre, limit) + end subPrefixAt + + /** Successively select each symbol in the `suffix` from `pre`, such that they are reachable. */ + def selectAll(pre: Type, suffix: Seq[Symbol]): Type = + suffix.foldLeft(pre)((pre, sym) => + pre.select( + if sym.isType && sym.is(Module) then sym.sourceModule + else sym + ) + ) + + def stripCommonPrefix(xs: List[Symbol], ys: List[Symbol]): (List[Symbol], List[Symbol]) = (xs, ys) match + case (x :: xs1, y :: ys1) if x eq y => stripCommonPrefix(xs1, ys1) + case _ => (xs, ys) + + val (parentRest, childRest) = stripCommonPrefix( + parent.owner.ownersIterator.toList.reverse, + child.owner.ownersIterator.toList.reverse + ) + + val commonPrefix = subPrefixAt(pre, parentRest.size) // unwind parent owners up to common prefix + + if commonPrefix.exists then selectAll(commonPrefix, childRest) + else NoType + + end childPrefix + end TypeOps diff --git a/compiler/src/dotty/tools/dotc/transform/PostInlining.scala b/compiler/src/dotty/tools/dotc/transform/PostInlining.scala index 47b670c5598b..0cfd3650ad0b 100644 --- a/compiler/src/dotty/tools/dotc/transform/PostInlining.scala +++ b/compiler/src/dotty/tools/dotc/transform/PostInlining.scala @@ -26,9 +26,7 @@ class PostInlining extends MacroTransform, IdentityDenotTransformer: override def transform(tree: Tree)(using Context): Tree = super.transform(tree) match case tree1: Template - if tree1.hasAttachment(ExtendsSingletonMirror) - || tree1.hasAttachment(ExtendsProductMirror) - || tree1.hasAttachment(ExtendsSumMirror) => + if tree1.hasAttachment(ExtendsSingletonMirror) || tree1.hasAttachment(ExtendsSumOrProductMirror) => synthMbr.addMirrorSupport(tree1) case tree1 => tree1 diff --git a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala index 95cf47fec4f8..0a6fa9217303 100644 --- a/compiler/src/dotty/tools/dotc/transform/SymUtils.scala +++ b/compiler/src/dotty/tools/dotc/transform/SymUtils.scala @@ -163,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(using Context): String = + def whyNotGenericSum(pre: Type)(using Context): String = if (!self.is(Sealed)) s"it is not a sealed ${self.kindString}" else if (!self.isOneOf(AbstractOrTrait)) @@ -171,20 +171,34 @@ object SymUtils: else { val children = self.children val companionMirror = self.useCompanionAsSumMirror + val ownerScope = if pre.isInstanceOf[SingletonType] then pre.classSymbol else NoSymbol def problem(child: Symbol) = { - def isAccessible(sym: Symbol): Boolean = - (self.isContainedIn(sym) && (companionMirror || ctx.owner.isContainedIn(sym))) - || sym.is(Module) && isAccessible(sym.owner) + def accessibleMessage(sym: Symbol): String = + def inherits(sym: Symbol, scope: Symbol): Boolean = + !scope.is(Package) && (scope.derivesFrom(sym) || inherits(sym, scope.owner)) + def isVisibleToParent(sym: Symbol): Boolean = + self.isContainedIn(sym) || sym.is(Module) && isVisibleToParent(sym.owner) + def isVisibleToScope(sym: Symbol): Boolean = + def isReachable: Boolean = ctx.owner.isContainedIn(sym) + def isMemberOfPrefix: Boolean = + ownerScope.exists && inherits(sym, ownerScope) + isReachable || isMemberOfPrefix || sym.is(Module) && isVisibleToScope(sym.owner) + if !isVisibleToParent(sym) then i"to its parent $self" + else if !companionMirror && !isVisibleToScope(sym) then i"to call site ${ctx.owner}" + else "" + end accessibleMessage + + val childAccessible = accessibleMessage(child.owner) if (child == self) "it has anonymous or inaccessible subclasses" - else if (!isAccessible(child.owner)) i"its child $child is not accessible" + else if (!childAccessible.isEmpty) i"its child $child is not accessible $childAccessible" else if (!child.isClass) "" // its a singleton enum value else { val s = child.whyNotGenericProduct if s.isEmpty then s else if child.is(Sealed) then - val s = child.whyNotGenericSum + val s = child.whyNotGenericSum(pre) if s.isEmpty then s else i"its child $child is not a generic sum because $s" else @@ -195,7 +209,7 @@ object SymUtils: else children.map(problem).find(!_.isEmpty).getOrElse("") } - def isGenericSum(using Context): Boolean = whyNotGenericSum.isEmpty + def isGenericSum(pre: Type)(using Context): Boolean = whyNotGenericSum(pre).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 79ebb1d4e72b..0a9a7a83948c 100644 --- a/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala +++ b/compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala @@ -18,14 +18,15 @@ import NullOpsDecorator._ object SyntheticMembers { + enum MirrorImpl: + case OfProduct(pre: Type) + case OfSum(childPres: List[Type]) + /** Attachment marking an anonymous class as a singleton case that will extend from Mirror.Singleton */ val ExtendsSingletonMirror: Property.StickyKey[Unit] = new Property.StickyKey /** Attachment recording that an anonymous class should extend Mirror.Product */ - val ExtendsProductMirror: Property.StickyKey[Unit] = new Property.StickyKey - - /** Attachment recording that an anonymous class should extend Mirror.Sum */ - val ExtendsSumMirror: Property.StickyKey[Unit] = new Property.StickyKey + val ExtendsSumOrProductMirror: Property.StickyKey[MirrorImpl] = new Property.StickyKey } /** Synthetic method implementations for case classes, case objects, @@ -484,9 +485,20 @@ class SyntheticMembers(thisPhase: DenotTransformer) { * type MirroredMonoType = C[?] * ``` */ - def fromProductBody(caseClass: Symbol, param: Tree)(using Context): Tree = { - val (classRef, methTpe) = - caseClass.primaryConstructor.info match { + def fromProductBody(caseClass: Symbol, param: Tree, optInfo: Option[MirrorImpl.OfProduct])(using Context): Tree = + def extractParams(tpe: Type): List[Type] = + tpe.asInstanceOf[MethodType].paramInfos + + def computeFromCaseClass: (Type, List[Type]) = + val (baseRef, baseInfo) = + val rawRef = caseClass.typeRef + val rawInfo = caseClass.primaryConstructor.info + optInfo match + case Some(info) => + (rawRef.asSeenFrom(info.pre, caseClass.owner), rawInfo.asSeenFrom(info.pre, caseClass.owner)) + case _ => + (rawRef, rawInfo) + baseInfo match case tl: PolyType => val (tl1, tpts) = constrained(tl, untpd.EmptyTree, alwaysAddTypeVars = true) val targs = @@ -494,22 +506,20 @@ class SyntheticMembers(thisPhase: DenotTransformer) { tpt.tpe match { case tvar: TypeVar => tvar.instantiate(fromBelow = false) } - (caseClass.typeRef.appliedTo(targs), tl.instantiate(targs)) + (baseRef.appliedTo(targs), extractParams(tl.instantiate(targs))) case methTpe => - (caseClass.typeRef, methTpe) - } - methTpe match { - case methTpe: MethodType => - val elems = - for ((formal, idx) <- methTpe.paramInfos.zipWithIndex) yield { - val elem = - param.select(defn.Product_productElement).appliedTo(Literal(Constant(idx))) - .ensureConforms(formal.translateFromRepeated(toArray = false)) - if (formal.isRepeatedParam) ctx.typer.seqToRepeated(elem) else elem - } - New(classRef, elems) - } - } + (baseRef, extractParams(methTpe)) + end computeFromCaseClass + + val (classRefApplied, paramInfos) = computeFromCaseClass + val elems = + for ((formal, idx) <- paramInfos.zipWithIndex) yield + val elem = + param.select(defn.Product_productElement).appliedTo(Literal(Constant(idx))) + .ensureConforms(formal.translateFromRepeated(toArray = false)) + if (formal.isRepeatedParam) ctx.typer.seqToRepeated(elem) else elem + New(classRefApplied, elems) + end fromProductBody /** For an enum T: * @@ -527,24 +537,36 @@ class SyntheticMembers(thisPhase: DenotTransformer) { * a wildcard for each type parameter. The normalized type of an object * O is O.type. */ - def ordinalBody(cls: Symbol, param: Tree)(using Context): Tree = - if (cls.is(Enum)) param.select(nme.ordinal).ensureApplied - else { + def ordinalBody(cls: Symbol, param: Tree, optInfo: Option[MirrorImpl.OfSum])(using Context): Tree = + if cls.is(Enum) then + param.select(nme.ordinal).ensureApplied + else + def computeChildTypes: List[Type] = + def rawRef(child: Symbol): Type = + if (child.isTerm) child.reachableTermRef else child.reachableRawTypeRef + optInfo match + case Some(info) => info + .childPres + .lazyZip(cls.children) + .map((pre, child) => rawRef(child).asSeenFrom(pre, child.owner)) + case _ => + cls.children.map(rawRef) + end computeChildTypes + val childTypes = computeChildTypes val cases = - for ((child, idx) <- cls.children.zipWithIndex) yield { - val patType = if (child.isTerm) child.reachableTermRef else child.reachableRawTypeRef + for (patType, idx) <- childTypes.zipWithIndex yield val pat = Typed(untpd.Ident(nme.WILDCARD).withType(patType), TypeTree(patType)) CaseDef(pat, EmptyTree, Literal(Constant(idx))) - } + Match(param.annotated(New(defn.UncheckedAnnot.typeRef, Nil)), cases) - } + end ordinalBody /** - If `impl` is the companion of a generic sum, add `deriving.Mirror.Sum` parent * and `MirroredMonoType` and `ordinal` members. * - If `impl` is the companion of a generic product, add `deriving.Mirror.Product` parent * and `MirroredMonoType` and `fromProduct` members. - * - If `impl` is marked with one of the attachments ExtendsSingletonMirror, ExtendsProductMirror, - * or ExtendsSumMirror, remove the attachment and generate the corresponding mirror support, + * - If `impl` is marked with one of the attachments ExtendsSingletonMirror or ExtendsSumOfProductMirror, + * remove the attachment and generate the corresponding mirror support, * On this case the represented class or object is referred to in a pre-existing `MirroredMonoType` * member of the template. */ @@ -581,30 +603,33 @@ class SyntheticMembers(thisPhase: DenotTransformer) { } def makeSingletonMirror() = addParent(defn.Mirror_SingletonClass.typeRef) - def makeProductMirror(cls: Symbol) = { + def makeProductMirror(cls: Symbol, optInfo: Option[MirrorImpl.OfProduct]) = { addParent(defn.Mirror_ProductClass.typeRef) addMethod(nme.fromProduct, MethodType(defn.ProductClass.typeRef :: Nil, monoType.typeRef), cls, - fromProductBody(_, _).ensureConforms(monoType.typeRef)) // t4758.scala or i3381.scala are examples where a cast is needed + fromProductBody(_, _, optInfo).ensureConforms(monoType.typeRef)) // t4758.scala or i3381.scala are examples where a cast is needed } - def makeSumMirror(cls: Symbol) = { + def makeSumMirror(cls: Symbol, optInfo: Option[MirrorImpl.OfSum]) = { addParent(defn.Mirror_SumClass.typeRef) addMethod(nme.ordinal, MethodType(monoType.typeRef :: Nil, defn.IntType), cls, - ordinalBody(_, _)) + ordinalBody(_, _, optInfo)) } if (clazz.is(Module)) { if (clazz.is(Case)) makeSingletonMirror() - else if (linked.isGenericProduct) makeProductMirror(linked) - else if (linked.isGenericSum) makeSumMirror(linked) + else if (linked.isGenericProduct) makeProductMirror(linked, None) + else if (linked.isGenericSum(NoType)) makeSumMirror(linked, None) else if (linked.is(Sealed)) - derive.println(i"$linked is not a sum because ${linked.whyNotGenericSum}") + derive.println(i"$linked is not a sum because ${linked.whyNotGenericSum(NoType)}") } else if (impl.removeAttachment(ExtendsSingletonMirror).isDefined) makeSingletonMirror() - else if (impl.removeAttachment(ExtendsProductMirror).isDefined) - makeProductMirror(monoType.typeRef.dealias.classSymbol) - else if (impl.removeAttachment(ExtendsSumMirror).isDefined) - makeSumMirror(monoType.typeRef.dealias.classSymbol) + else + impl.removeAttachment(ExtendsSumOrProductMirror).match + case Some(prodImpl: MirrorImpl.OfProduct) => + makeProductMirror(monoType.typeRef.dealias.classSymbol, Some(prodImpl)) + case Some(sumImpl: MirrorImpl.OfSum) => + makeSumMirror(monoType.typeRef.dealias.classSymbol, Some(sumImpl)) + case _ => cpy.Template(impl)(parents = newParents, body = newBody) } diff --git a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala index f3098cfd673f..2e49e0c8bb58 100644 --- a/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Synthesizer.scala @@ -235,7 +235,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): /** Create an anonymous class `new Object { type MirroredMonoType = ... }` * and mark it with given attachment so that it is made into a mirror at PostTyper. */ - private def anonymousMirror(monoType: Type, attachment: Property.StickyKey[Unit], span: Span)(using Context) = + private def anonymousMirror(monoType: Type, impl: MirrorImpl, span: Span)(using Context) = if ctx.isAfterTyper then ctx.compilationUnit.needsMirrorSupport = true val monoTypeDef = untpd.TypeDef(tpnme.MirroredMonoType, untpd.TypeTree(monoType)) val newImpl = untpd.Template( @@ -244,7 +244,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): derived = Nil, self = EmptyValDef, body = monoTypeDef :: Nil - ).withAttachment(attachment, ()) + ).withAttachment(ExtendsSumOrProductMirror, impl) typer.typed(untpd.New(newImpl).withSpan(span)) /** The mirror type @@ -296,7 +296,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): monoMap(mirroredType.resultType) private[Synthesizer] enum MirrorSource: - case ClassSymbol(cls: Symbol) + case ClassSymbol(pre: Type, cls: Symbol) case Singleton(src: Symbol, tref: TermRef) case GenericTuple(tps: List[Type]) @@ -304,7 +304,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): infix def sameTuple(that: MirrorSource)(using Context): Boolean = def arity(msrc: MirrorSource): Int = msrc match case GenericTuple(tps) => tps.size - case ClassSymbol(cls) if defn.isTupleClass(cls) => cls.typeParams.size // tested in tests/pos/i13859.scala + case ClassSymbol(_, cls) if defn.isTupleClass(cls) => cls.typeParams.size // tested in tests/pos/i13859.scala case _ => -1 def equivalent(n: Int, m: Int) = n == m && n > 0 @@ -315,14 +315,14 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): */ 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(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 case _ => this sameTuple that def show(using Context): String = this match - case ClassSymbol(cls) => i"$cls" + case ClassSymbol(_, cls) => i"$cls" case Singleton(src, _) => i"$src" case GenericTuple(tps) => val arity = tps.size @@ -344,7 +344,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val singleton = sym.sourceModule Right(MirrorSource.Singleton(singleton, TermRef(tp.prefix, singleton))) else - Right(MirrorSource.ClassSymbol(sym)) + Right(MirrorSource.ClassSymbol(tp.prefix, sym)) else reduce(tp.superType) case tp: TermRef => @@ -402,7 +402,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): def newTupleMirror(arity: Int): Tree = New(defn.RuntimeTupleMirrorTypeRef, Literal(Constant(arity)) :: Nil) - def makeProductMirror(cls: Symbol, tps: Option[List[Type]]): TreeWithErrors = + def makeProductMirror(pre: Type, cls: Symbol, tps: Option[List[Type]]): TreeWithErrors = val accessors = cls.caseAccessors.filterNot(_.isAllOf(PrivateLocal)) val elemLabels = accessors.map(acc => ConstantType(Constant(acc.name.toString))) val typeElems = tps.getOrElse(accessors.map(mirroredType.resultType.memberInfo(_).widenExpr)) @@ -423,7 +423,7 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val mirrorRef = if cls.useCompanionAsProductMirror then companionPath(mirroredType, span) else if defn.isTupleClass(cls) then newTupleMirror(typeElems.size) // TODO: cls == defn.PairClass when > 22 - else anonymousMirror(monoType, ExtendsProductMirror, span) + else anonymousMirror(monoType, MirrorImpl.OfProduct(pre), span) withNoErrors(mirrorRef.cast(mirrorType)) end makeProductMirror @@ -446,12 +446,14 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): case MirrorSource.GenericTuple(tps) => val maxArity = Definitions.MaxTupleArity val arity = tps.size - if tps.size <= maxArity then makeProductMirror(defn.TupleType(arity).nn.classSymbol, Some(tps)) + if tps.size <= maxArity then + val tupleCls = defn.TupleType(arity).nn.classSymbol + makeProductMirror(tupleCls.owner.reachableThisType, tupleCls, Some(tps)) else val reason = s"it reduces to a tuple with arity $arity, expected arity <= $maxArity" withErrors(i"${defn.PairClass} is not a generic product because $reason") - case MirrorSource.ClassSymbol(cls) => - if cls.isGenericProduct then makeProductMirror(cls, None) + case MirrorSource.ClassSymbol(pre, cls) => + if cls.isGenericProduct then makeProductMirror(pre, cls, None) 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") @@ -459,27 +461,41 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): private def sumMirror(mirroredType: Type, formal: Type, span: Span)(using Context): TreeWithErrors = - 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) + val (acceptableMsg, pre, cls) = MirrorSource.reduce(mirroredType) match + case Right(MirrorSource.Singleton(_, tp)) => (i"its subpart `$tp` is a term reference", NoType, NoSymbol) + case Right(MirrorSource.ClassSymbol(pre, cls)) => ("", pre, cls) case Right(MirrorSource.GenericTuple(tps)) => val arity = tps.size val cls = if arity <= Definitions.MaxTupleArity then defn.TupleType(arity).nn.classSymbol else defn.PairClass - ("", cls) - case Left(msg) => (msg, NoSymbol) + ("", NoType, cls) + case Left(msg) => (msg, NoType, NoSymbol) - val clsIsGenericSum = cls.isGenericSum + val clsIsGenericSum = cls.isGenericSum(pre) if acceptableMsg.isEmpty && clsIsGenericSum then val elemLabels = cls.children.map(c => ConstantType(Constant(c.name.toString))) - def solve(sym: Symbol): Type = sym match + def internalError(msg: => String)(using Context): Unit = + report.error(i"""Internal error when synthesizing sum mirror for $cls: + |$msg""".stripMargin, ctx.source.atSpan(span)) + + def childPrefix(child: Symbol)(using Context): Type = + val symPre = TypeOps.childPrefix(pre, cls, child) + if !symPre.exists then + internalError(i"unable to refine child prefix for $child from $pre") + symPre + + def refineAtPrefix(childPre: Type, child: Symbol, original: Type): Type = + if childPre.exists then original.asSeenFrom(childPre, child.owner) else original + + def solve(childPre: Type, child: Symbol): Type = child match case childClass: ClassSymbol => assert(childClass.isOneOf(Case | Sealed)) if childClass.is(Module) then - childClass.sourceModule.termRef + val module = childClass.sourceModule + refineAtPrefix(childPre, module, module.termRef) else - childClass.primaryConstructor.info match + refineAtPrefix(childPre, childClass, childClass.primaryConstructor.info) match case info: PolyType => // Compute the the full child type by solving the subtype constraint // `C[X1, ..., Xn] <: P`, where @@ -498,22 +514,32 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): val tparams = poly.paramRefs val variances = childClass.typeParams.map(_.paramVarianceSign) val instanceTypes = tparams.lazyZip(variances).map((tparam, variance) => - TypeComparer.instanceType(tparam, fromBelow = variance < 0)) - resType.substParams(poly, instanceTypes) + TypeComparer.instanceType(tparam, fromBelow = variance < 0) + ) + val instanceType = resType.substParams(poly, instanceTypes) + // this is broken in tests/run/i13332intersection.scala, + // because type parameters are not correctly inferred. + instanceType + end instantiate instantiate(using ctx.fresh.setExploreTyperState().setOwner(childClass)) - case _ => - childClass.typeRef - case child => child.termRef + case methTpe => + methTpe.finalResultType + case child => refineAtPrefix(childPre, child, child.termRef) end solve + val (childPres, childTypes) = cls.children.map(c => + val childPre = childPrefix(c) + childPre -> solve(childPre, c) + ).unzip + val (monoType, elemsType) = mirroredType match case mirroredType: HKTypeLambda => val elems = mirroredType.derivedLambdaType( - resType = TypeOps.nestedPairs(cls.children.map(solve)) + resType = TypeOps.nestedPairs(childTypes) ) (mkMirroredMonoType(mirroredType), elems) case _ => - val elems = TypeOps.nestedPairs(cls.children.map(solve)) + val elems = TypeOps.nestedPairs(childTypes) (mirroredType, elems) val mirrorType = @@ -525,12 +551,12 @@ class Synthesizer(typer: Typer)(using @constructorOnly c: Context): } val mirrorRef = if cls.useCompanionAsSumMirror then companionPath(mirroredType, span) - else anonymousMirror(monoType, ExtendsSumMirror, span) + else anonymousMirror(monoType, MirrorImpl.OfSum(childPres), 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 - withErrors(i"$cls is not a generic sum because ${cls.whyNotGenericSum}") + withErrors(i"$cls is not a generic sum because ${cls.whyNotGenericSum(pre)}") else EmptyTreeNoError end sumMirror diff --git a/compiler/test/dotc/pos-test-pickling.blacklist b/compiler/test/dotc/pos-test-pickling.blacklist index 3bfec0e4e160..8adbb38df743 100644 --- a/compiler/test/dotc/pos-test-pickling.blacklist +++ b/compiler/test/dotc/pos-test-pickling.blacklist @@ -28,6 +28,7 @@ seqtype-cycle # type of super reference changes due to late addition of Mirror.Singleton i939.scala +i13332super.scala # Match types i7872.scala diff --git a/docs/_docs/reference/contextual/derivation-macro.md b/docs/_docs/reference/contextual/derivation-macro.md index 3689018335dd..be7565616913 100644 --- a/docs/_docs/reference/contextual/derivation-macro.md +++ b/docs/_docs/reference/contextual/derivation-macro.md @@ -31,7 +31,7 @@ given derived[T: Type](using Quotes): Expr[Eq[T]] and for comparison reasons we give the same signature we had with `inline`: ```scala -inline given derived[T]: (m: Mirror.Of[T]) => Eq[T] = ??? +inline given derived[T](using Mirror.Of[T]): Eq[T] = ??? ``` Note, that since a type is used in a subsequent stage it will need to be lifted diff --git a/docs/_docs/reference/contextual/derivation.md b/docs/_docs/reference/contextual/derivation.md index f196f964e399..a81df53d573d 100644 --- a/docs/_docs/reference/contextual/derivation.md +++ b/docs/_docs/reference/contextual/derivation.md @@ -29,17 +29,28 @@ We say that `Tree` is the _deriving type_ and that the `Eq`, `Ordering` and `Sho ### Types supporting `derives` clauses All data types can have a `derives` clause. This document focuses primarily on data types which also have a given instance -of the `Mirror` type class available. Instances of the `Mirror` type class are generated automatically by the compiler -for, - -+ enums and enum cases -+ case classes and case objects -+ sealed classes or traits that have only case classes and case objects as children +of the `Mirror` type class available. `Mirror` type class instances provide information at the type level about the components and labelling of the type. They also provide minimal term level infrastructure to allow higher level libraries to provide comprehensive derivation support. +Instances of the `Mirror` type class are generated automatically by the compiler +unconditionally for: +- enums and enum cases, +- case objects. + +Instances for `Mirror` are also generated conditionally for: +- case classes where the constructor is visible at the callsite (always true if the companion is not a case object) +- sealed classes and sealed traits where: + - there exists at least one child case, + - each child case is reachable from the parent's definition, + - if the sealed trait/class has no companion, then each child case is reachable from the callsite through the prefix of the type being mirrored, + - and where the compiler can generate a `Mirror` type class instance for each child case. + + +The `Mirror` type class definition is as follows: + ```scala sealed trait Mirror: @@ -119,11 +130,11 @@ new Mirror.Product: new Leaf(...) ``` -If a Mirror cannot be generated automatically for a given type, an error will appear explaining why it is neither a supported +If a Mirror cannot be generated automatically for a given type, an error will appear explaining why it is neither a supported sum type nor a product type. For example, if `A` is a trait that is not sealed, ``` -No given instance of type deriving.Mirror.Of[A] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[A]: +No given instance of type deriving.Mirror.Of[A] was found for parameter x of method summon in object Predef. Failed to synthesize an instance of type deriving.Mirror.Of[A]: * trait A is not a generic product because it is not a case class * trait A is not a generic sum because it is not a sealed trait ``` @@ -133,6 +144,7 @@ Note the following properties of `Mirror` types, + Properties are encoded using types rather than terms. This means that they have no runtime footprint unless used and also that they are a compile time feature for use with Scala 3's metaprogramming facilities. ++ There is no restriction against the mirrored type being a local or inner class. + The kinds of `MirroredType` and `MirroredElemTypes` match the kind of the data type the mirror is an instance for. This allows `Mirror`s to support ADTs of all kinds. + There is no distinct representation type for sums or products (ie. there is no `HList` or `Coproduct` type as in @@ -155,7 +167,7 @@ following form, ```scala import scala.deriving.Mirror -def derived[T](using Mirror.Of[T]): TC[T] = ... +inline def derived[T](using Mirror.Of[T]): TC[T] = ... ``` That is, the `derived` method takes a context parameter of (some subtype of) type `Mirror` which defines the shape of diff --git a/sbt-test/scala2-compat/i13332/app/App.scala b/sbt-test/scala2-compat/i13332/app/App.scala new file mode 100644 index 000000000000..f141057fb873 --- /dev/null +++ b/sbt-test/scala2-compat/i13332/app/App.scala @@ -0,0 +1,16 @@ +import collectionstrawman.* + +import scala.deriving.Mirror + +def syntheticSumMirror = { + val m = new ListModule() + + val mIList = summon[Mirror.Of[m.IList[Int]]] + val mICons = summon[Mirror.Of[Tuple.Head[mIList.MirroredElemTypes]]] +} + +def syntheticProductMirror = { + val m = new ListModule() + + val mIPair = summon[Mirror.Of[m.IPair[Int, String]]] +} \ No newline at end of file diff --git a/sbt-test/scala2-compat/i13332/build.sbt b/sbt-test/scala2-compat/i13332/build.sbt new file mode 100644 index 000000000000..3d24ba92abe1 --- /dev/null +++ b/sbt-test/scala2-compat/i13332/build.sbt @@ -0,0 +1,14 @@ +val scala3Version = sys.props("plugin.scalaVersion") +val scala2Version = sys.props("plugin.scala2Version") + +lazy val lib = project.in(file("lib")) + .settings( + scalaVersion := scala2Version + ) + +lazy val app = project.in(file("app")) + .dependsOn(lib) + .settings( + scalaVersion := scala3Version, + scalacOptions += "-Xprint:inlining" + ) \ No newline at end of file diff --git a/sbt-test/scala2-compat/i13332/lib/Lib.scala b/sbt-test/scala2-compat/i13332/lib/Lib.scala new file mode 100644 index 000000000000..0c30bbfd648b --- /dev/null +++ b/sbt-test/scala2-compat/i13332/lib/Lib.scala @@ -0,0 +1,13 @@ +package collectionstrawman + +class ListModule { + + sealed trait IList[A] + case class ICons[A](head: A, next: IList[A]) extends IList[A] + case object INil extends IList[Nothing] + + case class IPair[A, B](a: A, b: B) + + case object IUnit + +} \ No newline at end of file diff --git a/sbt-test/scala2-compat/i13332/test b/sbt-test/scala2-compat/i13332/test new file mode 100644 index 000000000000..e048b0d85fe9 --- /dev/null +++ b/sbt-test/scala2-compat/i13332/test @@ -0,0 +1 @@ +> app/compile \ No newline at end of file diff --git a/tests/neg/anon-mirror-gen-local.scala b/tests/neg/anon-mirror-gen-local.scala new file mode 100644 index 000000000000..4105f48817a4 --- /dev/null +++ b/tests/neg/anon-mirror-gen-local.scala @@ -0,0 +1,26 @@ +import scala.deriving.Mirror + +class Outer1 { + + sealed trait Item + + object Wrapper { + case object A extends Item + } + + class Inaccessible { + case object B extends Item + } + + val M = summon[Mirror.Of[Item]] // error: B is not accessible from Item + +} + +class Outer2 { + sealed trait Item + case object A extends Item +} + +def testOuter2 = + // discussion point: should we allow this, or only allow singleton prefix? + val m_Outer2_Item = summon[Mirror.Of[Outer2#Item]] // error: Item is not accessible from m_Outer2_Item \ No newline at end of file diff --git a/tests/neg/i10997.scala b/tests/neg/i10997.scala index 6d37587d6b10..f12606ee1df9 100644 --- a/tests/neg/i10997.scala +++ b/tests/neg/i10997.scala @@ -15,4 +15,4 @@ class ClassWrapper { @main def Test = val cw = new ClassWrapper() - val mirrorParent = summon[deriving.Mirror.Of[cw.Base]] // error: code gen for Mirror can not access each case + val mirrorParent = summon[deriving.Mirror.Of[cw.Base]] // ok diff --git a/tests/neg/i13332shapeless.scala b/tests/neg/i13332shapeless.scala new file mode 100644 index 000000000000..4f11fb60e82b --- /dev/null +++ b/tests/neg/i13332shapeless.scala @@ -0,0 +1,97 @@ +// derived from shapeless 2 tests at https://github.com/milessabin/shapeless/blob/ae9fc166082adb3fa590a604b256c9e87a467c56/core/src/test/scala/shapeless/generic.scala + +import shapeless.impl.* + +object PathVariantDefns { + sealed trait AtomBase { + sealed trait Atom + case class Zero(value: String) extends Atom + } + + trait Atom1 extends AtomBase { + case class One(value: String) extends Atom + } + + trait Atom2 extends AtomBase { + case class Two(value: String) extends Atom + } + + object Atoms01 extends AtomBase with Atom1 + object Atoms02 extends AtomBase with Atom2 +} + +object PathVariants { + import PathVariantDefns._ + + val gen1 = Generic[Atoms01.Atom] // error: we do not support this yet + implicitly[gen1.Repr =:= (Atoms01.One :+: Atoms01.Zero :+: CNil)] // error: knock on + + val gen2 = Generic[Atoms02.Atom] // error: we do not support this yet + implicitly[gen2.Repr =:= (Atoms02.Two :+: Atoms02.Zero :+: CNil)] // error: knock on +} + + +////// ADAPTER CODE //////// + +package shapeless { + object impl { + import scala.deriving.Mirror + + def assertEquals(a: Any, b: Any): Unit = + assert(a == b, s"$a != $b") + + def assertTypedEquals[T](a: T, b: T): Unit = + assertEquals(a, b) + + def typed[T](expr: T): Unit = () + + type HList = Tuple + type HNil = EmptyTuple + type ::[+H, +T <: HList] = *:[H, T] + + case class CList(e: Any) + class CNil private () extends CList(EmptyTuple) + class :+:[+H, +T <: CList](elem: Any) extends CList(elem) + + def Inl[E](elem: E): E :+: Nothing = :+:(elem) + def Inr[Elems <: CList](elems: Elems): Nothing :+: Elems = :+:(elems.e) + + type CListRefl[T <: Tuple] = T match { + case EmptyTuple => CNil + case h *: tl => h :+: CListRefl[tl] + } + + extension [T](gen: Generic[T])(using m: Mirror.Of[T]) + transparent inline def to(t: T) = + inline compiletime.erasedValue[m.type] match + case _: Mirror.ProductOf[T] => + Tuple.fromProduct(t.asInstanceOf[Product]).asInstanceOf[m.MirroredElemTypes] + case _: Mirror.SumOf[T] => + Inl(t).asInstanceOf[CListRefl[m.MirroredElemTypes]] + + transparent inline def from[R](r: R) = + inline compiletime.erasedValue[m.type] match + case _: Mirror.ProductOf[T] => + m.asInstanceOf[Mirror.ProductOf[T]].fromProduct(r.asInstanceOf[Product]).asInstanceOf[T] + case _: Mirror.SumOf[T] => + r.asInstanceOf[CList].e.asInstanceOf[T] + + trait Generic[T]: + type Repr + + object Generic: + + type Repr[T, M, Elems] = M match + case Mirror.Sum { type MirroredType = T } => CListRefl[Elems] + case Mirror.Product { type MirroredType = T } => Elems + + transparent inline given [T](using m: Mirror.Of[T]): Generic[T] = apply[T] + + transparent inline def apply[T](using m: Mirror.Of[T]) = new Generic[T] { + type Repr = Generic.Repr[T, m.type, m.MirroredElemTypes] + } + + transparent inline def materialize[T, R](using m: Mirror.Of[T])(using + R <:< Repr[T, m.type, m.MirroredElemTypes]): Unit = () + } +} diff --git a/tests/pos/i10997.scala b/tests/pos/i10997.scala index fd9562f77d9e..bfe34422378f 100644 --- a/tests/pos/i10997.scala +++ b/tests/pos/i10997.scala @@ -22,3 +22,8 @@ object Test2 { } } + +object Test3 { + val cw = new Test() + val mirrorParent = summon[deriving.Mirror.Of[cw.Parent]] +} \ No newline at end of file diff --git a/tests/pos/i11174minimisation.scala b/tests/pos/i11174minimisation.scala new file mode 100644 index 000000000000..c40c34552bb8 --- /dev/null +++ b/tests/pos/i11174minimisation.scala @@ -0,0 +1,25 @@ +import scala.compiletime.{summonFrom, summonInline, erasedValue} +import scala.deriving.Mirror + +trait EnumerateNames[T] + +object EnumerateNames { + inline def derived[T]: EnumerateNames[T] = + summonFrom { + case ev: Mirror.Of[T] => + inline ev match + case m: Mirror.ProductOf[T] => ??? + case m: Mirror.SumOf[T] => + inline erasedValue[m.MirroredElemTypes] match + case _: (tpe *: _) => summonInline[EnumerateNames[tpe]] + case _: EmptyTuple => + ??? + } +} + +class MainClass { + enum Shape: + case Point + inline given auto[T]: EnumerateNames[T] = EnumerateNames.derived[T] + def shapeNames: EnumerateNames[Shape] = EnumerateNames.derived[Shape] +} \ No newline at end of file diff --git a/tests/pos/i13332.scala b/tests/pos/i13332.scala new file mode 100644 index 000000000000..67cd3356aeed --- /dev/null +++ b/tests/pos/i13332.scala @@ -0,0 +1,16 @@ +import scala.deriving.Mirror + +class Scope extends IListDefn { + + type Of = Mirror { type MirroredType[X] = IList[X]; type MirroredMonoType = IList[Any] ; type MirroredElemTypes[_] <: Tuple } + + val M = summon[Of] + +} + + +trait IListDefn { + sealed trait IList[A] // crucially, no companion is defined + case class INil[A]() extends IList[A] + case class ICons[A](h: A, t: IList[A]) extends IList[A] +} diff --git a/tests/pos/i13332super.scala b/tests/pos/i13332super.scala new file mode 100644 index 000000000000..1ab695d5d4a5 --- /dev/null +++ b/tests/pos/i13332super.scala @@ -0,0 +1,14 @@ +import scala.deriving.Mirror + +trait MixinAMini { + lazy val mixinB = new MixinBMini() {} +} +trait MixinBMini { + sealed trait Lst // crucially, no companion is defined + case class Cn(h: Int, t: Lst) extends Lst + case object Nl extends Lst +} +trait SubABMini extends MixinAMini with MixinBMini { + val mirror_SubABMini_super_mixinB_Lst = + summon[Mirror.Of[SubABMini.super[MixinAMini].mixinB.Lst]] +} diff --git a/tests/pos/i13935.scala b/tests/pos/i13935.scala new file mode 100644 index 000000000000..71cc770ad11f --- /dev/null +++ b/tests/pos/i13935.scala @@ -0,0 +1,6 @@ +import scala.deriving.Mirror +// Works: +sealed trait HasFoo { sealed trait Foo; case object B extends Foo; summon[Mirror.SumOf[Foo]] } + +// Doesn't work: +final class UsesFoo extends HasFoo { summon[Mirror.SumOf[Foo]] } \ No newline at end of file diff --git a/tests/run/anon-mirror-gen-local.scala b/tests/run/anon-mirror-gen-local.scala new file mode 100644 index 000000000000..68fb9500d5ba --- /dev/null +++ b/tests/run/anon-mirror-gen-local.scala @@ -0,0 +1,160 @@ +import scala.deriving.Mirror + +class Outer3 { self => + + object Inner { + sealed trait Item { // both children and parent share a common sub-prefix + + final lazy val mItem = summon[Mirror.Of[Item]] + final lazy val mA = + type TA = Tuple.Elem[mItem.MirroredElemTypes, 0] + summon[Mirror.Of[TA]] + final lazy val mB = + type TB = Tuple.Elem[mItem.MirroredElemTypes, 1] + summon[Mirror.Of[TB]] + } + + case class A() extends self.Inner.Item + case object A // force anonymous mirror + case class B() extends self.Inner.Item + } + +} + +def testOuter3 = + val o = Outer3() + val a = o.Inner.A() + val b = o.Inner.B() + assert(b.mItem.ordinal(a) == 0) + assert(b.mItem.ordinal(b) == 1) + assert(b.mA.fromProduct(EmptyTuple) == a) + assert(b.mB.fromProduct(EmptyTuple) == b) + +class Outer4 { + enum Item { + case A, B + } +} + +def testOuter4 = + val o = new Outer4() + val mItem = summon[Mirror.Of[o.Item]] + type derivedA = Tuple.Head[mItem.MirroredElemTypes] + val mA = summon[Mirror.Of[derivedA]] + assert(mItem.ordinal(o.Item.A) == 0) + assert(mA.fromProduct(EmptyTuple) == o.Item.A) + +class Outer5 { self => + + sealed trait Item + object Wrap { + sealed trait Fruit extends Item + object Fruit { + case object Apple extends Item with Fruit + case object Orange extends Item with Fruit + } + } + + lazy val o = new Outer5() // infinite init + +} + +def testOuter5 = + val o5 = new Outer5() + val mFruit = summon[Mirror.Of[o5.o.Item & o5.o.Wrap.Fruit]] + type derivedApple = Tuple.Head[mFruit.MirroredElemTypes] + val mApple = summon[Mirror.Of[derivedApple]] + + assert(mFruit.ordinal(o5.o.Wrap.Fruit.Apple) == 0) + assert(mApple.fromProduct(EmptyTuple) == o5.o.Wrap.Fruit.Apple) + +class Outer6 { + + sealed trait Item + object Wrap { + case class Fruit(seed: Seed) extends Item + case class Seed() extends Item + } + + final lazy val o = new Outer6() // infinite init + + def hello: Unit = { + + val mFruit = summon[Mirror.Of[o.Item & o.Wrap.Fruit]] + type derivedSeed = Tuple.Head[mFruit.MirroredElemTypes] + val mSeed = summon[Mirror.Of[derivedSeed]] + + assert(mFruit.fromProduct(Tuple(o.Wrap.Seed())) == o.Wrap.Fruit(o.Wrap.Seed())) + assert(mSeed.fromProduct(EmptyTuple) == o.Wrap.Seed()) // careful to ensure that correct outer is captured + } + +} + +def testOuter6 = { + val o = new Outer6() + o.hello +} + +def locally1 = { + + object Bar { + case object A extends Foo.Item + } + + object Foo { + + sealed trait Item { + + final lazy val mItem = summon[Mirror.Of[Item.this.type]] + + } + } + + assert(Bar.A.mItem.ordinal(Bar.A) == 0) + +} + +def locally2: Unit = { + + sealed trait Item + + object Wrapper { + case object A extends Item + case object B extends Item + } + + val mItem = summon[Mirror.Of[Item]] + type derivedA = Tuple.Head[mItem.MirroredElemTypes] + val mA = summon[Mirror.Of[derivedA]] + + assert(mItem.ordinal(Wrapper.A) == 0) + assert(mA.fromProduct(EmptyTuple) == Wrapper.A) + +} + +def locally3 = { + + class Foo { + final val foo: Qux.type = Qux + case object Qux + } + + class Bar extends Foo { + + def hello = + val mQux = summon[Mirror.Of[Bar.super.foo.type]] + assert(mQux.fromProduct(EmptyTuple) == Qux) + } + + val b = new Bar() + b.hello +} + +@main def Test = + testOuter3 + testOuter4 + testOuter5 + testOuter6 + locally1 + locally2 + locally3 \ No newline at end of file diff --git a/tests/run/i11174.scala b/tests/run/i11174.scala new file mode 100644 index 000000000000..644d3144d8d8 --- /dev/null +++ b/tests/run/i11174.scala @@ -0,0 +1,49 @@ +import compiletime.{summonFrom, erasedValue} + +import deriving.Mirror + +object EnumerateNames { + + inline def summonNext[T] = + summonFrom { + case en: EnumerateNames[T] => en + } + + inline def walkThrough[Types <: Tuple]: List[String] = + inline erasedValue[Types] match + case _: (tpe *: tpes) => + summonNext[tpe].apply +: walkThrough[tpes] + case _: EmptyTuple => + Nil + + + inline def derived[T]: EnumerateNames[T] = + summonFrom { + case ev: Mirror.Of[T] => + new EnumerateNames[T] { + def apply = + inline ev match + case m: Mirror.ProductOf[T] => walkThrough[m.MirroredElemTypes].mkString(", ") + case m: Mirror.SumOf[T] => walkThrough[m.MirroredElemTypes].mkString(", ") + } + } +} + +trait EnumerateNames[T] { + def apply: String +} + +class MainClass { + enum Shape: + case Square(width: Int, height: Int) extends Shape + case Circle(radius: Int) extends Shape + + given EnumerateNames[Int] with { + def apply: String = "int" + } + inline given auto[T]:EnumerateNames[T] = EnumerateNames.derived + def deriveEnumerateNames[T](using en: EnumerateNames[T]) = en.apply + def run: Unit = assert( deriveEnumerateNames[Shape] == "int, int, int" ) +} + +@main def Test = new MainClass().run \ No newline at end of file diff --git a/tests/run/i11174local.scala b/tests/run/i11174local.scala new file mode 100644 index 000000000000..a0c9b8e253b9 --- /dev/null +++ b/tests/run/i11174local.scala @@ -0,0 +1,52 @@ +import compiletime.{summonFrom, erasedValue} + +import deriving.Mirror + +object EnumerateNames { + + inline def summonNext[T] = + summonFrom { + case en: EnumerateNames[T] => en + } + + inline def walkThrough[Types <: Tuple]: List[String] = + inline erasedValue[Types] match + case _: (tpe *: tpes) => + summonNext[tpe].apply +: walkThrough[tpes] + case _: EmptyTuple => + Nil + + + inline def derived[T]: EnumerateNames[T] = + summonFrom { + case ev: Mirror.Of[T] => + new EnumerateNames[T] { + def apply = + inline ev match + case m: Mirror.ProductOf[T] => walkThrough[m.MirroredElemTypes].mkString(", ") + case m: Mirror.SumOf[T] => walkThrough[m.MirroredElemTypes].mkString(", ") + } + } +} + +trait EnumerateNames[T] { + def apply: String +} + +class MainClass { + given EnumerateNames[Int] with { + def apply: String = "int" + } + inline given auto[T]: EnumerateNames[T] = EnumerateNames.derived + def deriveEnumerateNames[T](using en: EnumerateNames[T]) = en.apply + + def run = { + enum Shape: + case Square(width: Int, height: Int) extends Shape + case Circle(radius: Int) extends Shape + + assert( deriveEnumerateNames[Shape] == "int, int, int" ) + } +} + +@main def Test = new MainClass().run \ No newline at end of file diff --git a/tests/run/i12328.scala b/tests/run/i12328.scala new file mode 100644 index 000000000000..0f9f00ca19aa --- /dev/null +++ b/tests/run/i12328.scala @@ -0,0 +1,35 @@ +import scala.deriving.Mirror +import scala.compiletime._ + +trait Schema[T] + +object Schema { + inline def recurse[A <: Tuple]: List[Schema[Any]] = + inline erasedValue[A] match { + case _: (t *: ts) => summonInline[Schema[t]].asInstanceOf[Schema[Any]] :: recurse[ts] + case EmptyTuple => Nil + } + + inline def derived[T]: Schema[T] = + inline summonInline[Mirror.Of[T]] match { + case m: Mirror.SumOf[T] => + val subTypes = recurse[m.MirroredElemTypes] + new Schema[T] { } + case m: Mirror.ProductOf[T] => + val fields = recurse[m.MirroredElemTypes] + new Schema[T] { } + } + + inline given gen[T]: Schema[T] = derived +} + +@main def Test: Unit = { + + sealed trait Item + object Item { + case object A extends Item + case object B extends Item + } + + summon[Schema[Item]] +} \ No newline at end of file diff --git a/tests/run/i12328a.scala b/tests/run/i12328a.scala new file mode 100644 index 000000000000..eb48445ff5c9 --- /dev/null +++ b/tests/run/i12328a.scala @@ -0,0 +1,26 @@ +import scala.deriving.Mirror +import scala.compiletime._ + +@main def Test: Unit = { + + object Parent { + sealed trait Item + } + object Wrap { + case object A extends Parent.Item + case class B(i: Int, is: Parent.Item) extends Parent.Item + case class C(i: Int, is: Parent.Item) extends Parent.Item + case object C // force anon mirror + } + + val M_Item = summon[Mirror.Of[Parent.Item]] + assert(M_Item.ordinal(Wrap.A) == 0) + assert(M_Item.ordinal(Wrap.B(1, Wrap.A)) == 1) + assert(M_Item.ordinal(Wrap.C(2, Wrap.A)) == 2) + + val M_Wrap_B = summon[Mirror.Of[Tuple.Elem[M_Item.MirroredElemTypes, 1]]] + assert(M_Wrap_B.fromProduct((1, Wrap.A)) == Wrap.B(1, Wrap.A)) + + val M_Wrap_C = summon[Mirror.Of[Tuple.Elem[M_Item.MirroredElemTypes, 2]]] + assert(M_Wrap_C.fromProduct((1, Wrap.A)) == Wrap.C(1, Wrap.A)) +} \ No newline at end of file diff --git a/tests/run/i12328b.scala b/tests/run/i12328b.scala new file mode 100644 index 000000000000..acb17de89777 --- /dev/null +++ b/tests/run/i12328b.scala @@ -0,0 +1,22 @@ +import scala.deriving.Mirror +import scala.compiletime._ + +@main def Test: Unit = { + + sealed trait Item + case object A extends Item + case class B(i: Int, is: Item) extends Item + case class C(i: Int, is: Item) extends Item + case object C // force anon mirror + + val M_Item = summon[Mirror.Of[Item]] + assert(M_Item.ordinal(A) == 0) + assert(M_Item.ordinal(B(1, A)) == 1) + assert(M_Item.ordinal(C(2, A)) == 2) + + val M_B = summon[Mirror.Of[Tuple.Elem[M_Item.MirroredElemTypes, 1]]] + assert(M_B.fromProduct((1, A)) == B(1, A)) + + val M_C = summon[Mirror.Of[Tuple.Elem[M_Item.MirroredElemTypes, 2]]] + assert(M_C.fromProduct((1, A)) == C(1, A)) +} \ No newline at end of file diff --git a/tests/run/i13332.scala b/tests/run/i13332.scala new file mode 100644 index 000000000000..44c8ecfd00f8 --- /dev/null +++ b/tests/run/i13332.scala @@ -0,0 +1,23 @@ +import scala.deriving.Mirror + +class Scope extends IListDefn { + + type Of = Mirror { type MirroredType[X] = IList[X]; type MirroredMonoType = IList[Any] ; type MirroredElemTypes[_] <: Tuple } + + val M = summon[Of] + +} + + +trait IListDefn { + sealed trait IList[+A] // crucially, no companion is defined + case class INil[+A]() extends IList[A] + case class ICons[+A](h: A, t: IList[A]) extends IList[A] +} + +@main def Test = + val scope = Scope() + assert(scope.M.ordinal(scope.INil()) == 0) + + val M_ICons = summon[Mirror.Of[Tuple.Elem[scope.M.MirroredElemTypes[Int], 1]]] + assert(M_ICons.fromProduct((1, scope.INil())) == scope.ICons(1, scope.INil())) \ No newline at end of file diff --git a/tests/run/i13332a.scala b/tests/run/i13332a.scala new file mode 100644 index 000000000000..3478ed325467 --- /dev/null +++ b/tests/run/i13332a.scala @@ -0,0 +1,229 @@ +import scala.deriving.Mirror + +class Scope extends IListDefn { + + type OfK1[F[_], F0] = Mirror { + type MirroredType[X] = F[X] + type MirroredMonoType = F0 + type MirroredElemTypes[_] <: Tuple + } + + type Of = OfK1[IList, IList[?]] + + // Force the children to be visited. + type Foo = (Wrap.INil[Any], Wrap.ICons[Any], Wrap.Inner.Bottom[Any], Wrap.Inner.IZ[Any]) + + final lazy val mirror_Scope_this_IList = + summon[Of] + final lazy val mirror_Scope_this_Wrap_ICons = + type TICons = Tuple.Elem[mirror_Scope_this_IList.MirroredElemTypes[Any], 1] + summon[Mirror.Of[TICons]] + final lazy val mirror_Scope_this_Wrap_Inner_Bottom = + type TBottom = Tuple.Elem[mirror_Scope_this_IList.MirroredElemTypes[Any], 2] + summon[Mirror.Of[TBottom]] +} + +trait IListDefn { + sealed trait IList[A] // crucially, no companion is defined + object Wrap { + case class INil[A]() extends IList[A] + case class ICons[A](h: A, t: IList[A]) extends IList[A] + object Inner { + sealed trait Bottom[A] extends IList[A] + case class IZ[A]() extends Bottom[A] + } + } +} + +sealed trait Opt[+A] // crucially, no companion is defined +case class Sm[+A](a: A) extends Opt[A] +case object Nn extends Opt[Nothing] + +trait Module { + + sealed trait Lst // crucially, no companion is defined + case class Cn(h: Int, t: Lst) extends Lst + case object Nl extends Lst + + object knot extends Module + + final lazy val mirror_Module_this_knot_Lst = + // here `Cn` and `Nl` are simultaneously reachable to `test`, + // and also `knot` inherits from `Module`, there is no conflict here. + summon[Mirror.Of[knot.Lst]] +} + +class SubModule extends Module { + final lazy val mirror_SubModule_this_Lst = + summon[Mirror.Of[Lst]] + final lazy val mirror_SubModule_this_knot_Lst = + summon[Mirror.Of[knot.Lst]] +} + +trait ForkedExample { + object ParentHolder { + sealed trait Parent // crucially, no companion is defined + } + object ChildHolder { + case class Child(x: Int) extends ParentHolder.Parent + } +} + +class SubForkedExample extends ForkedExample { + final lazy val mirror_SubForkedExample_this_ParentHolder_Parent = + summon[Mirror.Of[ParentHolder.Parent]] + final lazy val mirror_SubForkedExample_this_ChildHolder_Child = + type TChild = Tuple.Head[mirror_SubForkedExample_this_ParentHolder_Parent.MirroredElemTypes] + summon[Mirror.Of[TChild]] +} + +trait BiggerKnot { + + sealed trait Lst // crucially, no companion is defined + case class Cn(h: Int, t: Lst) extends Lst + case object Nl extends Lst + + object Branch { + object knot extends Module + } + + + final lazy val mirror_BiggerKnot_this_Branch_knot_Lst = + // here `Cn` and `Nl` are simultaneously reachable to `test`, + // and also `knot` inherits from `Module`, there is no conflict here. + summon[Mirror.Of[Branch.knot.Lst]] +} + +class SubBiggerKnot extends BiggerKnot { + final lazy val mirror_SubBiggerKnot_this_Branch_knot_Lst = + summon[Mirror.Of[Branch.knot.Lst]] + final lazy val mirror_SubBiggerKnot_this_Branch_knot_Cn = + type TCn = Tuple.Head[mirror_SubBiggerKnot_this_Branch_knot_Lst.MirroredElemTypes] + summon[Mirror.Of[TCn]] +} + +trait Universe { + + trait Container { + object First { + object Nested { + sealed trait Lst // crucially, no companion is defined + } + } + object Sibling { + object Inner { + case class Cn(h: Int, t: First.Nested.Lst) extends First.Nested.Lst + case object Nl extends First.Nested.Lst + } + } + } + + class BranchImpl { + class SubContainer extends Container { + final lazy val mirror_SubContainer_this_First_Nested_Lst = + summon[Mirror.Of[First.Nested.Lst]] + + final lazy val mirror_SubContainer_this_Sibling_Inner_Cn = + type TCn = Tuple.Head[mirror_SubContainer_this_First_Nested_Lst.MirroredElemTypes] + summon[Mirror.Of[TCn]] + } + final lazy val impl = SubContainer() + } + + final lazy val Branch = BranchImpl() + + final lazy val mirror_Universe_this_Branch_impl_First_Nested_Lst = + summon[Mirror.Of[Branch.impl.First.Nested.Lst]] + + final lazy val mirror_Universe_this_Branch_impl_Sibling_Inner_Cn = + type TCn = Tuple.Head[mirror_Universe_this_Branch_impl_First_Nested_Lst.MirroredElemTypes] + summon[Mirror.Of[TCn]] +} + +class SubUniverse extends Universe { + final lazy val mirror_SubUniverse_this_Branch_impl_First_Nested_Lst = + summon[Mirror.Of[Branch.impl.First.Nested.Lst]] + final lazy val mirror_SubUniverse_this_Branch_impl_Sibling_Inner_Cn = + type TCn = Tuple.Head[mirror_SubUniverse_this_Branch_impl_First_Nested_Lst.MirroredElemTypes] + summon[Mirror.Of[TCn]] +} + +trait Whole { + trait MixinA { + lazy val mixinB = new MixinB() {} + } + trait MixinB { + object A extends MixinB { // by inheriting `MixinB`, we should not check for inheritance from the right + sealed trait Lst // crucially, no companion is defined + } + object Wrap { + case class Cn(h: Int, t: A.Lst) extends A.Lst + case object Nl extends A.Lst + } + object mixinA extends MixinA + } + trait SubAB extends MixinA with MixinB { + val mirror_SubAB_this_mixinB_mixinA_mixinB_A_Lst = + summon[Mirror.Of[SubAB.this.mixinB.mixinA.mixinB.A.Lst]] + + val mirror_SubAB_this_mixinB_mixinA_mixinB_Wrap_Cn = + type TCn = Tuple.Head[mirror_SubAB_this_mixinB_mixinA_mixinB_A_Lst.MirroredElemTypes] + summon[Mirror.Of[TCn]] + } +} + + +@main def Test = + locally { + val s = Scope() + val mirror_s_IList = + summon[Mirror.Of[s.IList[Any]]] + val mirror_s_Wrap_ICons = + type TICons = Tuple.Elem[mirror_s_IList.MirroredElemTypes, 1] + summon[Mirror.Of[TICons]] + val mirror_s_Wrap_Inner_Bottom = + type TBottom = Tuple.Elem[mirror_s_IList.MirroredElemTypes, 2] + summon[Mirror.Of[TBottom]] + } + locally { + val mirror_Opt = + summon[Mirror.Of[``.Opt[Any]]] // baseline + } + locally { + val m = new Module() {} + val mirror_m_Lst = + summon[Mirror.Of[m.Lst]] + val mirror_m_knot_Lst = + summon[Mirror.Of[m.knot.Lst]] + } + locally { + val sm = SubModule() + val mirror_sm_Lst = + summon[Mirror.Of[sm.Lst]] + val mirror_sm_knot_Lst = + summon[Mirror.Of[sm.knot.Lst]] + } + locally { + val sf = SubForkedExample() + val mirror_sf_ParentHolder_Parent = + summon[Mirror.Of[sf.ParentHolder.Parent]] + val mirror_sf_ChildHolder_Child = + type TChild = Tuple.Head[mirror_sf_ParentHolder_Parent.MirroredElemTypes] + summon[Mirror.Of[TChild]] + } + locally { + val sbk = SubBiggerKnot() + val mirror_sbk_Branch_knot_Lst = + summon[Mirror.Of[sbk.Branch.knot.Lst]] + val mirror_sbk_Branch_knot_Cn = + type TCn = Tuple.Head[mirror_sbk_Branch_knot_Lst.MirroredElemTypes] + summon[Mirror.Of[TCn]] + } + locally { + val su = SubUniverse() + val mirror_su_Branch_impl_First_Nested_Lst = + summon[Mirror.Of[su.Branch.impl.First.Nested.Lst]] + val mirror_su_Branch_impl_Sibling_Inner_Cn = + type TCn = Tuple.Head[mirror_su_Branch_impl_First_Nested_Lst.MirroredElemTypes] + summon[Mirror.Of[TCn]] + } diff --git a/tests/run/i13332anonprod.scala b/tests/run/i13332anonprod.scala new file mode 100644 index 000000000000..b4c4b0f55c3e --- /dev/null +++ b/tests/run/i13332anonprod.scala @@ -0,0 +1,24 @@ +import scala.deriving.Mirror + +class Scope extends IListDefn { + + type Of = Mirror { type MirroredType[X] = IList[X]; type MirroredMonoType = IList[Any] ; type MirroredElemTypes[_] <: Tuple } + + val M = summon[Of] + +} + + +trait IListDefn { + sealed trait IList[A] // crucially, no companion is defined + case class INil[A]() extends IList[A] + case class ICons[A](h: A, t: IList[A]) extends IList[A] + case object ICons // force anonymous mirror for ICons +} + +@main def Test = + val scope = Scope() + assert(scope.M.ordinal(scope.INil()) == 0) + + val M_ICons = summon[Mirror.Of[Tuple.Elem[scope.M.MirroredElemTypes[Int], 1]]] + assert(M_ICons.fromProduct((1, scope.INil())) == scope.ICons(1, scope.INil())) \ No newline at end of file diff --git a/tests/run/i13332intersection.scala b/tests/run/i13332intersection.scala new file mode 100644 index 000000000000..5ab5917ab815 --- /dev/null +++ b/tests/run/i13332intersection.scala @@ -0,0 +1,280 @@ +import scala.collection.mutable +import scala.annotation.tailrec + +// derived from tests/run/typeclass-derivation3.scala, but with hierarchical ADT as an inner class. + +class Datatypes { + import typeclasses.* + + sealed trait JsonSelect extends Selectable { self: JsonObject => + def selectDynamic(name: String): Json[Any] = + fields.collectFirst({ + case (`name`, value) => value + }).getOrElse(JsonNull) + } + + sealed trait JsonObjFactory[Ref]: + type T = JsonObject & Ref + def apply(fields: (String, Json[Any])*): T = new JsonObject(fields.toList).asInstanceOf[T] + + sealed abstract class Json[+A] + sealed abstract class JsonScalar[A] extends Json[A] + sealed abstract class JsonCompound[A] extends Json[A] + case class JsonObject(fields: List[(String, Json[Any])]) extends JsonCompound[Any] with JsonSelect + case class JsonArray[B <: Json[Any]](items: List[B]) extends JsonCompound[List[B]]: + export items.apply + case class JsonString(str: String) extends JsonScalar[String] + case class JsonNumber(num: Double) extends JsonScalar[Double] + sealed abstract class JsonBoolean extends JsonScalar[Boolean] + case object JsonTrue extends JsonBoolean + case object JsonFalse extends JsonBoolean + case object JsonNull extends JsonScalar[Null] + + def json[B <: Json[Any]](items: B*): JsonArray[B] = JsonArray(items.toList) + + object Json: + def apply[Ref] = new JsonObjFactory[Ref] {} + + sealed abstract class Lst[+A] // force anonymous mirror to be generated for Lst + case class Cs[+A](head: A, tail: Lst[A]) extends Lst[A] + case object Cs // force anonymous mirror to be generated for Cs + case object Nl extends Lst[Nothing] + +} + +object typeclasses { + + // Another type class + trait Pickler[T] { + def pickle(buf: mutable.ListBuffer[Int], x: T): Unit + def unpickle(buf: mutable.ListBuffer[Int]): T + } + + object Pickler { + import scala.compiletime.{erasedValue, constValue, summonFrom} + import compiletime.* + import deriving.* + + def nextInt(buf: mutable.ListBuffer[Int]): Int = try buf.head finally buf.dropInPlace(1) + + inline def tryPickle[T](buf: mutable.ListBuffer[Int], x: T): Unit = summonFrom { + case pkl: Pickler[T] => pkl.pickle(buf, x) + } + + inline def pickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], x: Product): Unit = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + tryPickle[elem](buf, x.productElement(n).asInstanceOf[elem]) + pickleElems[elems1](n + 1)(buf, x) + case _: EmptyTuple => + } + + transparent inline def pickleCases[Alts <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], x: Any, ord: Int): Unit = + inline erasedValue[Alts] match { + case _: (alt *: alts1) => + if (ord == n) + summonFrom { + case m: Mirror.ProductOf[`alt`] => pickleElems[m.MirroredElemTypes](0)(buf, x.asInstanceOf[Product]) + case m: Mirror.SumOf[`alt`] => + val ord1 = m.ordinal(x.asInstanceOf[alt]) + buf += ord1 + pickleCases[m.MirroredElemTypes](0)(buf, x, ord1) + } + else pickleCases[alts1](n + 1)(buf, x, ord) + case _: EmptyTuple => + } + + inline def tryUnpickle[T](buf: mutable.ListBuffer[Int]): T = summonFrom { + case pkl: Pickler[T] => pkl.unpickle(buf) + } + + inline def unpickleElems[Elems <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], elems: Array[Any]): Unit = + inline erasedValue[Elems] match { + case _: (elem *: elems1) => + elems(n) = tryUnpickle[elem](buf) + unpickleElems[elems1](n + 1)(buf, elems) + case _: EmptyTuple => + } + + transparent inline def unpickleCase[T, Elems <: Tuple](buf: mutable.ListBuffer[Int], m: Mirror.ProductOf[T]): T = { + inline val size = constValue[Tuple.Size[Elems]] + inline if (size == 0) + m.fromProduct(EmptyTuple) + else { + val elems = new Array[Any](size) + unpickleElems[Elems](0)(buf, elems) + m.fromProduct(new Product { + def canEqual(that: Any): Boolean = true + def productArity: Int = size + def productElement(idx: Int): Any = elems(idx) + }) + } + } + + transparent inline def unpickleCases[T, Alts <: Tuple](n: Int)(buf: mutable.ListBuffer[Int], ord: Int): T = + inline erasedValue[Alts] match { + case _: (alt *: alts1) => + if (ord == n) + summonFrom { + case m: Mirror.ProductOf[`alt` & T] => + unpickleCase[`alt` & T, m.MirroredElemTypes](buf, m) + case m: Mirror.SumOf[`alt` & T] => + val ord1 = nextInt(buf) + unpickleCases[`alt` & T, m.MirroredElemTypes](0)(buf, ord1) + } + else unpickleCases[T, alts1](n + 1)(buf, ord) + case _: EmptyTuple => + throw new IndexOutOfBoundsException(s"unexpected ordinal number: $ord") + } + + transparent inline def derived[T](implicit ev: Mirror.Of[T]): Pickler[T] = new { + def pickle(buf: mutable.ListBuffer[Int], x: T): Unit = + inline ev match { + case m: Mirror.SumOf[T] => + val ord = m.ordinal(x) + buf += ord + pickleCases[m.MirroredElemTypes](0)(buf, x, ord) + case m: Mirror.ProductOf[T] => + pickleElems[m.MirroredElemTypes](0)(buf, x.asInstanceOf[Product]) + } + def unpickle(buf: mutable.ListBuffer[Int]): T = + inline ev match { + case m: Mirror.SumOf[T] => + val ord = nextInt(buf) + unpickleCases[T, m.MirroredElemTypes](0)(buf, ord) + case m: Mirror.ProductOf[T] => + unpickleCase[T, m.MirroredElemTypes](buf, m) + } + } + + implicit def listPickler[T: Pickler]: Pickler[List[T]] = Pickler.derived + + implicit def tuple2pickler[T: Pickler, U: Pickler]: Pickler[(T, U)] = Pickler.derived + + implicit object IntPickler extends Pickler[Int] { + def pickle(buf: mutable.ListBuffer[Int], x: Int): Unit = buf += x + def unpickle(buf: mutable.ListBuffer[Int]): Int = nextInt(buf) + } + + implicit object NullPickler extends Pickler[Null] { + def pickle(buf: mutable.ListBuffer[Int], x: Null): Unit = buf += -1 + def unpickle(buf: mutable.ListBuffer[Int]): Null = { val _ = nextInt(buf).ensuring(_ == -1); null } + } + + implicit object DoublePickler extends Pickler[Double] { + def pickle(buf: mutable.ListBuffer[Int], x: Double): Unit = + val asLong = java.lang.Double.doubleToLongBits(x) + buf += ((asLong >> 32) & 0xFFFFFFFFL).toInt + buf += (asLong & 0xFFFFFFFFL).toInt + def unpickle(buf: mutable.ListBuffer[Int]): Double = + val high = nextInt(buf) + val low = nextInt(buf) + java.lang.Double.longBitsToDouble(((high.toLong << 32) | low.toLong) & 0xFFFFFFFFFFFFFFFFL) + } + + implicit object StringPickler extends Pickler[String] { + def pickle(buf: mutable.ListBuffer[Int], x: String): Unit = { + val compressed = x.getBytes.sliding(4, 4).map(_.foldLeft(0)((a, b) => (a << 8) | b)).toArray + buf += compressed.length + buf ++= compressed + } + def unpickle(buf: mutable.ListBuffer[Int]): String = { + val len = nextInt(buf) + val arr = new Array[Int](len) + buf.copyToArray(arr) + buf.dropInPlace(len) + val bbuf = new mutable.ArrayBuffer[Byte] + val temp = new Array[Byte](4) + def intToBytes(x: Int, temp: Array[Byte]): Unit = { + var i = 0 + var z = x + while z != 0 do // fill temp with bytes until no more in `z` + temp(i) = (z & 0xFF).toByte + i += 1 + z = z >> 8 + i -= 1 // set i to last filled index + while i >= 0 do + bbuf += temp(i) + i -= 1 + } + arr.foreach(intToBytes(_, temp)) + new String(bbuf.toArray) + } + } + } + +} +import typeclasses.* +// Tests +@main def Test = { + val datatypes = new Datatypes() + import datatypes.* + + type OfLst = deriving.Mirror { + type MirroredType[X] = Lst[X]; + type MirroredMonoType = Lst[?]; + type MirroredElemTypes[_] <: Tuple + } + + type OfCs = deriving.Mirror { + type MirroredType[X] = Cs[X]; + type MirroredMonoType = Cs[?]; + type MirroredElemTypes[_] <: Tuple + } + + val M_Lst = summon[OfLst] + val M_Cs = summon[OfCs] + + val buf = new mutable.ListBuffer[Int] + + implicit def pklLst[T: Pickler]: Pickler[Lst[T]] = Pickler.derived + val pklLstInt = pklLst[Int] + val lst = Cs(1, Cs(2, Cs(3, Nl))) + pklLstInt.pickle(buf, lst) + val lst1 = pklLstInt.unpickle(buf) + assert(lst == lst1) + + val Food = Json[{ + val kind: JsonString + val isSandwich: JsonBoolean + val brand: JsonString | JsonNull.type + }] + + val Person = Json[{ + val name: JsonString + val age: JsonNumber + val favFoods: JsonArray[Food.T] + }] + + val People: JsonArray[Person.T] = json( + Person( + "name" -> JsonString("Alice"), + "age" -> JsonNumber(25), + "favFoods" -> json( + Food( + "kind" -> JsonString("Pizza"), + "isSandwich" -> JsonFalse, + ), + Food( + "kind" -> JsonString("Hotdog"), + "isSandwich" -> JsonTrue, // lol + "brand" -> JsonString("Coop") + ) + ) + ) + ) + + implicit def pklJson[T <: Json[Any]]: Pickler[T] = Pickler.derived + val pklPeople = pklJson[JsonArray[Person.T]] + pklPeople.pickle(buf, People) + val People1 = pklPeople.unpickle(buf) + assert(People == People1) + assert(People1(0).name.str == "Alice") + assert(People1(0).age.num == 25.0) + assert(People1(0).favFoods(0).kind.str == "Pizza") + assert(People1(0).favFoods(0).isSandwich == JsonFalse) + assert(People1(0).favFoods(0).brand == JsonNull) + assert(People1(0).favFoods(1).kind.str == "Hotdog") + assert(People1(0).favFoods(1).isSandwich == JsonTrue) + assert(People1(0).favFoods(1).brand == JsonString("Coop")) +} diff --git a/tests/run/i13332shapeless-b.scala b/tests/run/i13332shapeless-b.scala new file mode 100644 index 000000000000..9dbb42d07b7a --- /dev/null +++ b/tests/run/i13332shapeless-b.scala @@ -0,0 +1,33 @@ +def outer3Local = { + class Wrapper { + object Nested { + sealed trait Color + } + } + val wrapper = new Wrapper + import wrapper.Nested.Color + + object Inner { + case object Red extends Color + case object Green extends Color + case object Blue extends Color + case class Rgb(hex: Int) extends Color + case object Rgb // force anonymous mirror for Rgb + } + + import Inner.* + val M = summon[deriving.Mirror.Of[Color]] + assert(M.ordinal(Red) == 0) + assert(M.ordinal(Green) == 1) + assert(M.ordinal(Blue) == 2) + assert(M.ordinal(Rgb(0xffaaff)) == 3) + + val M_Rgb = + type TRgb = Tuple.Elem[M.MirroredElemTypes, 3] + summon[deriving.Mirror.Of[TRgb]] + + assert(M_Rgb.fromProduct(Tuple(0xffaaff)) == Rgb(0xffaaff)) +} + +@main def Test = + outer3Local diff --git a/tests/run/i13332shapeless.scala b/tests/run/i13332shapeless.scala new file mode 100644 index 000000000000..204980d8fe62 --- /dev/null +++ b/tests/run/i13332shapeless.scala @@ -0,0 +1,374 @@ +// derived from shapeless 2 tests at https://github.com/milessabin/shapeless/blob/ae9fc166082adb3fa590a604b256c9e87a467c56/core/src/test/scala/shapeless/generic.scala + +import shapeless.impl.* + +trait Parent { + case class Nested(i: Int, s: String) + + sealed abstract class Foo extends Product with Serializable + + case object A extends Foo + case object B extends Foo + case class C() extends Foo +} + +trait Child extends Parent { + val gen = Generic[Nested] + val adtGen = Generic[Foo] +} + +object O extends Child + +def testNestedInherited: Unit = { + val n0 = O.Nested(23, "foo") + val repr = O.gen.to(n0) + typed[Int :: String :: HNil](repr) + val n1 = O.gen.from(repr) + typed[O.Nested](n1) + assertEquals(n0, n1) + + { + val foo0 = O.B + val repr = O.adtGen.to(foo0) + typed[O.A.type :+: O.B.type :+: O.C :+: CNil](repr) + } + + { + val foo0 = O.C() + val repr = O.adtGen.to(foo0) + typed[O.A.type :+: O.B.type :+: O.C :+: CNil](repr) + } +} + +def testPathViaObject: Unit = { + sealed trait T + object T { + case class C(i: Int) extends T + case object O extends T + } + + type Repr = T.C :+: T.O.type :+: CNil + val gen = Generic[T] + val c = T.C(42) + val injC: Repr = Inl(c) + val injO: Repr = Inr(Inl(T.O)) + + assertTypedEquals[Repr](injC, gen.to(c)) + assertTypedEquals[T](c, gen.from(injC)) + assertTypedEquals[Repr](injO, gen.to(T.O)) + assertTypedEquals[T](T.O, gen.from(injO)) +} + +def testPathViaObjectNoCompanion: Unit = { + sealed trait T + object Wrap { + case class C(i: Int) extends T + case object O extends T + } + + type Repr = Wrap.C :+: Wrap.O.type :+: CNil + val gen = Generic[T] + val c = Wrap.C(42) + val injC: Repr = Inl(c) + val injO: Repr = Inr(Inl(Wrap.O)) + + assertTypedEquals[Repr](injC, gen.to(c)) + assertTypedEquals[T](c, gen.from(injC)) + assertTypedEquals[Repr](injO, gen.to(Wrap.O)) + assertTypedEquals[T](Wrap.O, gen.from(injO)) +} + +def testPathViaSubPrefix: Unit = { + class Outer { + class Inner { + sealed trait T + } + + val inner = new Inner + case class C(i: Int) extends inner.T + case object O extends inner.T + + final lazy val genThis = Generic[inner.T] + } + + class SubOuter extends Outer { + lazy val genSubThis = Generic[inner.T] + } + + val outer = new Outer + type Repr = outer.C :+: outer.O.type :+: CNil + val gen = Generic[outer.inner.T] + val c = outer.C(42) + val injC: Repr = Inl(c) + val injO: Repr = Inr(Inl(outer.O)) + + assertTypedEquals[Repr](injC, gen.to(c)) + assertTypedEquals[outer.inner.T](c, gen.from(injC)) + assertTypedEquals[Repr](injO, gen.to(outer.O)) + assertTypedEquals[outer.inner.T](outer.O, gen.from(injO)) +} + +@main def Test = + testNestedInherited + testPathViaObject + testPathViaObjectNoCompanion + testPathViaSubPrefix + +package GenericTestsAux2 { + + class Bar[A] + + object Bar { + given gen[A](using Generic[A]): Bar[A] = Bar() + } + + class Outer1 { + sealed trait Color + object Inner { + case object Red extends Color + } + + implicitly[Bar[Color]] + } + + object Outer2 { + // We support this one because the child types are static - so this should have always compiled. + // if `Outer2` was a trait, this would fail in `whyNotGenericSum`. + class Wrapper { + sealed trait Color + } + val wrapper = new Wrapper + import wrapper.Color + case object Red extends Color + case object Green extends Color + case object Blue extends Color + + implicitly[Bar[Color]] + } + + object Outer3 { + class Wrapper { + sealed trait Color + } + val wrapper = new Wrapper + case object Red extends wrapper.Color + case object Green extends wrapper.Color + case object Blue extends wrapper.Color + + implicitly[Bar[wrapper.Color]] + } + + def outer3Local = { + class Wrapper { + object Nested { + sealed trait Color + } + } + val wrapper = new Wrapper + import wrapper.Nested.Color + case object Red extends Color + case object Green extends Color + case object Blue extends Color + + implicitly[Bar[Color]] + } + + object Outer4 { + val wrapper = new Wrapper + case object Red extends wrapper.Color + case object Green extends wrapper.Color + case object Blue extends wrapper.Color + + class Wrapper { + sealed trait Color + implicitly[Bar[wrapper.Color]] + } + } + + object Outer5 { + trait Command + object Command { + sealed trait Execution extends Command + } + case class Buzz() extends Command.Execution + case class Door() extends Command.Execution + Generic[Command.Execution] + } +} + +object MixedCCNonCCNested { + // Block local + { + object T1{ + sealed abstract class Tree + final case class Node(left: Tree, right: Tree, v: Int) extends Tree + case object Leaf extends Tree + } + + Generic[T1.Tree] + import T1._ + Generic[Tree] + + sealed trait A + sealed case class B(i: Int, s: String) extends A + case object C extends A + sealed trait D extends A + final case class E(a: Double, b: Option[Float]) extends D + case object F extends D + sealed abstract class Foo extends D + case object Baz extends Foo + // final class Bar extends Foo // Mirrors only support case classes + // final class Baz(val i1: Int, val s1: String) extends Foo // Mirrors only support case classes + + Generic[A] + Generic[B] + Generic[C.type] + Generic[D] + Generic[E] + Generic[F.type] + Generic[Foo] + Generic[Baz.type] + // Generic[Bar] // Mirrors only support case classes + // Generic[Baz] // Mirrors only support case classes + } + + def methodLocal: Unit = { + object T1{ + sealed abstract class Tree + final case class Node(left: Tree, right: Tree, v: Int) extends Tree + case object Leaf extends Tree + } + + Generic[T1.Tree] + import T1._ + Generic[Tree] + + sealed trait A + sealed case class B(i: Int, s: String) extends A + case object C extends A + sealed trait D extends A + final case class E(a: Double, b: Option[Float]) extends D + case object F extends D + sealed abstract class Foo extends D + case object Baz extends Foo + // final class Bar extends Foo // Mirrors only support case classes + // final class Baz(val i1: Int, val s1: String) extends Foo // Mirrors only support case classes + + Generic[A] + Generic[B] + Generic[C.type] + Generic[D] + Generic[E] + Generic[F.type] + Generic[Foo] + Generic[Baz.type] + // Generic[Bar] // Mirrors only support case classes + // Generic[Baz] // Mirrors only support case classes + } +} + +package TestPrefixes1 { + trait Defs { + case class CC(i: Int, s: String) + + sealed trait Sum + case class SumI(i: Int) extends Sum + case class SumS(s: String) extends Sum + } + + object Defs extends Defs + + object Derivations { + import shapeless.impl._ + + Generic[Defs.CC] + Generic[Defs.SumI] + Generic[Defs.SumS] + + Generic[Defs.Sum] + Generic.materialize[Defs.Sum, Defs.SumI :+: Defs.SumS :+: CNil] + } +} + +object PrivateCtorDefns { + sealed trait PublicFamily + case class PublicChild() extends PublicFamily + private case class PrivateChild() extends PublicFamily + + case class WrongApplySignature private(value: String) + object WrongApplySignature { + def apply(v: String): Either[String, WrongApplySignature] = Left("No ways") + } +} + +object PrivateCtor { + import PrivateCtorDefns._ + + Generic[PublicFamily] // in shapeless this was commented out, but it works here + Generic[WrongApplySignature] // in shapeless this was commented out, but it works here +} + +////// ADAPTER CODE //////// + +package shapeless { + object impl { + import scala.deriving.Mirror + + def assertEquals(a: Any, b: Any): Unit = + assert(a == b, s"$a != $b") + + def assertTypedEquals[T](a: T, b: T): Unit = + assertEquals(a, b) + + def typed[T](expr: T): Unit = () + + type HList = Tuple + type HNil = EmptyTuple + type ::[+H, +T <: HList] = *:[H, T] + + case class CList(e: Any) + class CNil private () extends CList(EmptyTuple) + class :+:[+H, +T <: CList](elem: Any) extends CList(elem) + + def Inl[E](elem: E): E :+: Nothing = :+:(elem) + def Inr[Elems <: CList](elems: Elems): Nothing :+: Elems = :+:(elems.e) + + type CListRefl[T <: Tuple] = T match { + case EmptyTuple => CNil + case h *: tl => h :+: CListRefl[tl] + } + + extension [T](gen: Generic[T])(using m: Mirror.Of[T]) + transparent inline def to(t: T) = + inline compiletime.erasedValue[m.type] match + case _: Mirror.ProductOf[T] => + Tuple.fromProduct(t.asInstanceOf[Product]).asInstanceOf[m.MirroredElemTypes] + case _: Mirror.SumOf[T] => + Inl(t).asInstanceOf[CListRefl[m.MirroredElemTypes]] + + transparent inline def from[R](r: R) = + inline compiletime.erasedValue[m.type] match + case _: Mirror.ProductOf[T] => + m.asInstanceOf[Mirror.ProductOf[T]].fromProduct(r.asInstanceOf[Product]).asInstanceOf[T] + case _: Mirror.SumOf[T] => + r.asInstanceOf[CList].e.asInstanceOf[T] + + trait Generic[T]: + type Repr + + object Generic: + + type Repr[T, M, Elems] = M match + case Mirror.Sum { type MirroredType = T } => CListRefl[Elems] + case Mirror.Product { type MirroredType = T } => Elems + + transparent inline given [T](using m: Mirror.Of[T]): Generic[T] = apply[T] + + transparent inline def apply[T](using m: Mirror.Of[T]) = new Generic[T] { + type Repr = Generic.Repr[T, m.type, m.MirroredElemTypes] + } + + transparent inline def materialize[T, R](using m: Mirror.Of[T])(using + R <:< Repr[T, m.type, m.MirroredElemTypes]): Unit = () + } +}