Skip to content

Commit

Permalink
Support Mirrors for local and inner classes.
Browse files Browse the repository at this point in the history
- update child accessibility check for anonymous
  mirrors in whyNotGenericSum. Now check the
  prefix at the callsite can access the child.
- for sum mirrors, compute a new prefix for each
  child from the callsite prefix of the parent,
  see TypeOps.childPrefix. For each child,
  subsititute its prefix at definition with the
  childPrefix using asSeenFrom. For polymorphic
  classes, perform the subsitution on the
  constructor before inferring constraints.
- add tests for issues 13332, 13935, 11174, 12328
- add tests for local/inner classes taken from
  Shapeless for its Generic type, backed
  by mirrors
  • Loading branch information
bishabosha committed Aug 11, 2022
1 parent e560c2d commit c47b6b8
Show file tree
Hide file tree
Showing 30 changed files with 1,757 additions and 84 deletions.
69 changes: 69 additions & 0 deletions compiler/src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 1 addition & 3 deletions compiler/src/dotty/tools/dotc/transform/PostInlining.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
28 changes: 21 additions & 7 deletions compiler/src/dotty/tools/dotc/transform/SymUtils.scala
Original file line number Diff line number Diff line change
Expand Up @@ -163,28 +163,42 @@ 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))
"it is not an abstract class"
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
Expand All @@ -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 =
Expand Down
109 changes: 67 additions & 42 deletions compiler/src/dotty/tools/dotc/transform/SyntheticMembers.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -484,32 +485,41 @@ 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 =
for (tpt <- tpts) yield
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:
*
Expand All @@ -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.
*/
Expand Down Expand Up @@ -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)
}
Expand Down
Loading

0 comments on commit c47b6b8

Please sign in to comment.