Skip to content

Improve opaque types #6567

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Jun 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 43 additions & 52 deletions compiler/src/dotty/tools/dotc/ast/Desugar.scala
Original file line number Diff line number Diff line change
Expand Up @@ -818,47 +818,6 @@ object desugar {
}
}

/** Expand
*
* <mods> opaque type T = [Xs] =>> R
*
* to
*
* <mods> opaque type T = T.T
* synthetic object T {
* synthetic opaque type T >: [Xs] =>> R
* }
*
* The generated companion object will later (in Namer) be merged with the user-defined
* companion object, and the synthetic opaque type member will go into the self type.
*/
def opaqueAlias(tdef: TypeDef)(implicit ctx: Context): Tree =
if (lacksDefinition(tdef)) {
ctx.error(em"opaque type ${tdef.name} must be an alias type", tdef.sourcePos)
tdef.withFlags(tdef.mods.flags &~ Opaque)
}
else {
def completeForwarder(fwd: Tree) = tdef.rhs match {
case LambdaTypeTree(tparams, tpt) =>
val tparams1 =
for (tparam <- tparams)
yield tparam.withMods(tparam.mods | Synthetic)
lambdaAbstract(tparams1,
AppliedTypeTree(fwd, tparams.map(tparam => Ident(tparam.name))))
case _ =>
fwd
}
val moduleName = tdef.name.toTermName
val localRef = Select(Ident(moduleName), tdef.name).withAttachment(SuppressAccessCheck, ())
val aliasType = cpy.TypeDef(tdef)(rhs = completeForwarder(localRef)).withSpan(tdef.span.startPos)
val localType = tdef.withMods(Modifiers(Synthetic | Opaque).withPrivateWithin(tdef.name))

val companions = moduleDef(ModuleDef(
moduleName, Template(emptyConstructor, Nil, Nil, EmptyValDef, localType :: Nil))
.withFlags(Synthetic | Opaque))
Thicket(aliasType :: companions.toList)
}

/** The normalized name of `mdef`. This means
* 1. Check that the name does not redefine a Scala core class.
* If it does redefine, issue an error and return a mangled name instead of the original one.
Expand Down Expand Up @@ -1035,19 +994,51 @@ object desugar {
Bind(name, Ident(nme.WILDCARD)).withSpan(tree.span)
}

def defTree(tree: Tree)(implicit ctx: Context): Tree = tree match {
case tree: ValDef => valDef(tree)
case tree: TypeDef =>
if (tree.isClassDef) classDef(tree)
else if (tree.mods.is(Opaque, butNot = Synthetic)) opaqueAlias(tree)
else tree
case tree: DefDef =>
if (tree.name.isConstructorName) tree // was already handled by enclosing classDef
else defDef(tree)
case tree: ModuleDef => moduleDef(tree)
case tree: PatDef => patDef(tree)
/** The type of tests that check whether a MemberDef is OK for some flag.
* The test succeeds if the partial function is defined and returns true.
*/
type MemberDefTest = PartialFunction[MemberDef, Boolean]

val legalOpaque: MemberDefTest = {
case TypeDef(_, rhs) =>
def rhsOK(tree: Tree): Boolean = tree match {
case _: TypeBoundsTree | _: Template => false
case LambdaTypeTree(_, body) => rhsOK(body)
case _ => true
}
rhsOK(rhs)
}

/** Check that modifiers are legal for the definition `tree`.
* Right now, we only check for `opaque`. TODO: Move other modifier checks here.
*/
def checkModifiers(tree: Tree)(implicit ctx: Context): Tree = tree match {
case tree: MemberDef =>
var tested: MemberDef = tree
def fail(msg: String) = ctx.error(msg, tree.sourcePos)
def checkApplicable(flag: FlagSet, test: MemberDefTest): Unit =
if (tested.mods.is(flag) && !test.applyOrElse(tree, _ => false)) {
fail(i"modifier `$flag` is not allowed for this definition")
tested = tested.withMods(tested.mods.withoutFlags(flag))
}
checkApplicable(Opaque, legalOpaque)
tested
case _ =>
tree
}

def defTree(tree: Tree)(implicit ctx: Context): Tree =
checkModifiers(tree) match {
case tree: ValDef => valDef(tree)
case tree: TypeDef =>
if (tree.isClassDef) classDef(tree) else tree
case tree: DefDef =>
if (tree.name.isConstructorName) tree // was already handled by enclosing classDef
else defDef(tree)
case tree: ModuleDef => moduleDef(tree)
case tree: PatDef => patDef(tree)
}

/** { stats; <empty > }
* ==>
* { stats; () }
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/Trees.scala
Original file line number Diff line number Diff line change
Expand Up @@ -818,6 +818,8 @@ object Trees {
extends ProxyTree[T] {
type ThisTree[-T >: Untyped] = Annotated[T]
def forwardTo: Tree[T] = arg
override def disableOverlapChecks = true
// disable overlaps checks since the WithBounds annotation swaps type and annotation.
}

trait WithoutTypeOrPos[-T >: Untyped] extends Tree[T] {
Expand Down
8 changes: 6 additions & 2 deletions compiler/src/dotty/tools/dotc/ast/tpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -206,8 +206,12 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
def DefDef(sym: TermSymbol, tparams: List[TypeSymbol], vparamss: List[List[TermSymbol]],
resultType: Type, rhs: Tree)(implicit ctx: Context): DefDef =
ta.assignType(
untpd.DefDef(sym.name, tparams map TypeDef, vparamss.nestedMap(ValDef(_)),
TypeTree(resultType), rhs),
untpd.DefDef(
sym.name,
tparams.map(tparam => TypeDef(tparam).withSpan(tparam.span)),
vparamss.nestedMap(vparam => ValDef(vparam).withSpan(vparam.span)),
TypeTree(resultType),
rhs),
sym)

def DefDef(sym: TermSymbol, rhs: Tree = EmptyTree)(implicit ctx: Context): DefDef =
Expand Down
5 changes: 5 additions & 0 deletions compiler/src/dotty/tools/dotc/ast/untpd.scala
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,11 @@ object untpd extends Trees.Instance[Untyped] with UntypedTreeInfo {
if (this.flags == flags) this
else copy(flags = flags)

def withoutFlags(flags: FlagSet): Modifiers =
if (this.is(flags))
Modifiers(this.flags &~ flags, this.privateWithin, this.annotations, this.mods.filterNot(_.flags.is(flags)))
else this

def withAddedMod(mod: Mod): Modifiers =
if (mods.exists(_ eq mod)) this
else withMods(mods :+ mod)
Expand Down
17 changes: 16 additions & 1 deletion compiler/src/dotty/tools/dotc/core/Annotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,27 @@ object Annotations {

def unapply(ann: Annotation)(implicit ctx: Context): Option[Symbol] =
if (ann.symbol == defn.ChildAnnot) {
val AppliedType(tycon, (arg: NamedType) :: Nil) = ann.tree.tpe
val AppliedType(_, (arg: NamedType) :: Nil) = ann.tree.tpe
Some(arg.symbol)
}
else None
}

/** Extractor for WithBounds[T] annotations */
object WithBounds {
def unapply(ann: Annotation)(implicit ctx: Context): Option[TypeBounds] =
if (ann.symbol == defn.WithBoundsAnnot) {
// We need to extract the type of the type tree in the New itself.
// The annotation's type has been simplified as the type of an expression,
// which means that `&` or `|` might have been lost.
// Test in pos/reference/opaque.scala
val Apply(TypeApply(Select(New(tpt), nme.CONSTRUCTOR), _), Nil) = ann.tree
val AppliedType(_, lo :: hi :: Nil) = tpt.tpe
Some(TypeBounds(lo, hi))
}
else None
}

def makeSourceFile(path: String)(implicit ctx: Context): Annotation =
apply(defn.SourceFileAnnot, Literal(Constant(path)))
}
Expand Down
15 changes: 3 additions & 12 deletions compiler/src/dotty/tools/dotc/core/CheckRealizable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -145,25 +145,16 @@ class CheckRealizable(implicit ctx: Context) {
*/
private def boundsRealizability(tp: Type) = {

def isOpaqueCompanionThis = tp match {
case tp: ThisType => tp.cls.isOpaqueCompanion
case _ => false
}

val memberProblems =
for {
mbr <- tp.nonClassTypeMembers
if !(mbr.info.loBound <:< mbr.info.hiBound) && !mbr.symbol.isOpaqueHelper
}
for mbr <- tp.nonClassTypeMembers if !(mbr.info.loBound <:< mbr.info.hiBound)
yield new HasProblemBounds(mbr.name, mbr.info)

val refinementProblems =
for {
for
name <- refinedNames(tp)
if (name.isTypeName)
mbr <- tp.member(name).alternatives
if !(mbr.info.loBound <:< mbr.info.hiBound) && !isOpaqueCompanionThis
}
if !(mbr.info.loBound <:< mbr.info.hiBound)
yield new HasProblemBounds(name, mbr.info)

def baseTypeProblems(base: Type) = base match {
Expand Down
2 changes: 2 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Definitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -877,6 +877,8 @@ class Definitions {
def BodyAnnot(implicit ctx: Context): ClassSymbol = BodyAnnotType.symbol.asClass
@threadUnsafe lazy val ChildAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.Child")
def ChildAnnot(implicit ctx: Context): ClassSymbol = ChildAnnotType.symbol.asClass
@threadUnsafe lazy val WithBoundsAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.WithBounds")
def WithBoundsAnnot(implicit ctx: Context): ClassSymbol = WithBoundsAnnotType.symbol.asClass
@threadUnsafe lazy val CovariantBetweenAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.CovariantBetween")
def CovariantBetweenAnnot(implicit ctx: Context): ClassSymbol = CovariantBetweenAnnotType.symbol.asClass
@threadUnsafe lazy val ContravariantBetweenAnnotType: TypeRef = ctx.requiredClassRef("scala.annotation.internal.ContravariantBetween")
Expand Down
14 changes: 2 additions & 12 deletions compiler/src/dotty/tools/dotc/core/Flags.scala
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ object Flags {
/** A mutable var */
final val Mutable: FlagSet = termFlag(12, "mutable")

/** An opqaue type */
/** An opaque type or a class containing one */
final val Opaque: FlagSet = typeFlag(12, "opaque")

final val MutableOrOpaque: FlagSet = Mutable.toCommonFlags
Expand Down Expand Up @@ -550,13 +550,7 @@ object Flags {
Accessor | AbsOverride | StableRealizable | Captured | Synchronized | Erased

/** Flags that can apply to a module class */
final val RetainedModuleClassFlags: FlagSet = RetainedModuleValAndClassFlags |
Enum | Opaque

/** Flags that are copied from a synthetic companion to a user-defined one
* when the two are merged. See: Namer.mergeCompanionDefs
*/
final val RetainedSyntheticCompanionFlags: FlagSet = Opaque
final val RetainedModuleClassFlags: FlagSet = RetainedModuleValAndClassFlags | Enum

/** Packages and package classes always have these flags set */
final val PackageCreationFlags: FlagSet =
Expand Down Expand Up @@ -687,9 +681,6 @@ object Flags {
/** A Java companion object */
final val JavaModule: FlagConjunction = allOf(JavaDefined, Module)

/** An opaque companion object */
final val OpaqueModule: FlagConjunction = allOf(Opaque, Module)

/** A Java companion object */
final val JavaProtected: FlagConjunction = allOf(JavaDefined, Protected)

Expand Down Expand Up @@ -739,7 +730,6 @@ object Flags {
final val SyntheticTermParam: FlagConjunction = allOf(Synthetic, TermParam)
final val SyntheticTypeParam: FlagConjunction = allOf(Synthetic, TypeParam)
final val SyntheticCase: FlagConjunction = allOf(Synthetic, Case)
final val SyntheticOpaque: FlagConjunction = allOf(Synthetic, Opaque)

implicit def conjToFlagSet(conj: FlagConjunction): FlagSet =
FlagSet(conj.bits)
Expand Down
77 changes: 39 additions & 38 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -372,18 +372,38 @@ object SymDenotations {
case _ => unforcedDecls.openForMutations
}

/** If this is a synthetic opaque type alias, mark it as Deferred with empty bounds
/** If this is a synthetic opaque type alias, mark it as Deferred with bounds
* as given by the right hand side's `WithBounds` annotation, if one is present,
* or with empty bounds of the right kind, otherwise.
* At the same time, integrate the original alias as a refinement of the
* self type of the enclosing class.
*/
final def normalizeOpaque()(implicit ctx: Context) = {
def abstractRHS(tp: Type): Type = tp match {
case tp: HKTypeLambda => tp.derivedLambdaType(resType = abstractRHS(tp.resType))
case _ => defn.AnyType
}
if (isOpaqueHelper) {
if (isOpaqueAlias) {
info match {
case TypeAlias(alias) =>
info = TypeBounds(defn.NothingType, abstractRHS(alias))
val (refiningAlias, bounds) = alias match {
case AnnotatedType(alias1, Annotation.WithBounds(bounds)) =>
(alias1, bounds)
case _ =>
(alias, TypeBounds(defn.NothingType, abstractRHS(alias)))
}
def refineSelfType(selfType: Type) =
RefinedType(selfType, name, TypeAlias(refiningAlias))
val enclClassInfo = owner.asClass.classInfo
enclClassInfo.selfInfo match {
case self: Type =>
owner.info = enclClassInfo.derivedClassInfo(selfInfo = refineSelfType(self))
case self: Symbol =>
self.info = refineSelfType(self.info)
}
info = bounds
setFlag(Deferred)
typeRef.recomputeDenot()
case _ =>
}
}
Expand Down Expand Up @@ -553,18 +573,14 @@ object SymDenotations {
final def isAbstractOrParamType(implicit ctx: Context): Boolean = this is DeferredOrTypeParam

/** Is this symbol a user-defined opaque alias type? */
def isOpaqueAlias(implicit ctx: Context): Boolean = is(Opaque, butNot = Synthetic)

/** Is this symbol the companion of an opaque alias type? */
def isOpaqueCompanion(implicit ctx: Context): Boolean = is(OpaqueModule)
def isOpaqueAlias(implicit ctx: Context): Boolean = is(Opaque) && !isClass

/** Is this symbol a synthetic opaque type inside an opaque companion object? */
def isOpaqueHelper(implicit ctx: Context): Boolean = is(SyntheticOpaque, butNot = Module)
/** Is this symbol a module that contains opaque aliases? */
def containsOpaques(implicit ctx: Context): Boolean = is(Opaque) && isClass

/** Can this symbol have a companion module?
* This is the case if it is a class or an opaque type alias.
*/
final def canHaveCompanion(implicit ctx: Context) = isClass || isOpaqueAlias
def seesOpaques(implicit ctx: Context): Boolean =
containsOpaques ||
is(Module, butNot = Package) && owner.containsOpaques

/** Is this the denotation of a self symbol of some class?
* This is the case if one of two conditions holds:
Expand Down Expand Up @@ -789,7 +805,7 @@ object SymDenotations {
*/
def membersNeedAsSeenFrom(pre: Type)(implicit ctx: Context): Boolean =
!( this.isTerm
|| this.isStaticOwner && !this.isOpaqueCompanion
|| this.isStaticOwner && !this.seesOpaques
|| ctx.erasedTypes
|| (pre eq NoPrefix)
|| (pre eq thisType)
Expand Down Expand Up @@ -1029,16 +1045,6 @@ object SymDenotations {
*/
final def companionModule(implicit ctx: Context): Symbol =
if (is(Module)) sourceModule
else if (isOpaqueAlias) {
def reference(tp: Type): Symbol = tp match {
case TypeRef(prefix: TermRef, _) => prefix.termSymbol
case tp: HKTypeLambda => reference(tp.resType)
case tp: AppliedType => reference(tp.tycon)
case tp: ErrorType => registeredCompanion.sourceModule
}
val TypeAlias(alias) = info
reference(alias)
}
else registeredCompanion.sourceModule

private def companionType(implicit ctx: Context): Symbol =
Expand All @@ -1053,13 +1059,6 @@ object SymDenotations {
final def companionClass(implicit ctx: Context): Symbol =
companionType.suchThat(_.isClass).symbol

/** The opaque type with the same (type-) name as this module or module class,
* and which is also defined in the same scope and compilation unit.
* NoSymbol if this type does not exist.
*/
final def companionOpaqueType(implicit ctx: Context): Symbol =
companionType.suchThat(_.isOpaqueAlias).symbol

final def scalacLinkedClass(implicit ctx: Context): Symbol =
if (this is ModuleClass) companionNamed(effectiveName.toTypeName)
else if (this.isClass) companionNamed(effectiveName.moduleClassName).sourceModule.moduleClass
Expand Down Expand Up @@ -1109,15 +1108,17 @@ object SymDenotations {
final def enclosingSubClass(implicit ctx: Context): Symbol =
ctx.owner.ownersIterator.findSymbol(_.isSubClass(symbol))

/** The alias of a synthetic opaque type that's stored in the self type of the
/** The alias of an opaque type alias that's stored in the self type of the
* containing object.
*/
def opaqueAlias(implicit ctx: Context): Type = {
if (isOpaqueHelper)
owner.asClass.classInfo.selfType match {
case RefinedType(_, _, bounds) => bounds.extractOpaqueAlias
}
else NoType
def recur(tp: Type): Type = tp match {
case RefinedType(parent, rname, TypeAlias(alias)) =>
if (rname == name) alias else recur(parent)
case _ =>
NoType
}
recur(owner.asClass.classInfo.selfType)
}

/** The non-private symbol whose name and type matches the type of this symbol
Expand Down Expand Up @@ -1974,7 +1975,7 @@ object SymDenotations {

/** Register companion class */
override def registerCompanion(companion: Symbol)(implicit ctx: Context) =
if (companion.canHaveCompanion && !unforcedIsAbsent && !companion.unforcedIsAbsent)
if (companion.isClass && !unforcedIsAbsent && !companion.unforcedIsAbsent)
myCompanion = companion

override def registeredCompanion(implicit ctx: Context) = { ensureCompleted(); myCompanion }
Expand Down
Loading