Skip to content

Opaque types - selftype encoding #5300

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 14 commits into from
Nov 12, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
Erasure of opaque types
Type erasure is computed before and after opaque types are eliminated; we need a scheme
that works in every case.
  • Loading branch information
odersky committed Nov 8, 2018
commit 646e596ab202110cc1b13557a0b0938e5604c011
79 changes: 46 additions & 33 deletions compiler/src/dotty/tools/dotc/core/TypeErasure.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Uniques.unique
import dotc.transform.ExplicitOuter._
import dotc.transform.ValueClasses._
import transform.TypeUtils._
import Decorators._
import Definitions.MaxImplementedFunctionArity
import scala.annotation.tailrec

Expand All @@ -32,8 +33,8 @@ import scala.annotation.tailrec
*/
object TypeErasure {

private def erasureDependsOnArgs(tp: Type)(implicit ctx: Context) =
tp.isRef(defn.ArrayClass) || tp.isRef(defn.PairClass)
private def erasureDependsOnArgs(sym: Symbol)(implicit ctx: Context) =
sym == defn.ArrayClass || sym == defn.PairClass

/** A predicate that tests whether a type is a legal erased type. Only asInstanceOf and
* isInstanceOf may have types that do not satisfy the predicate.
Expand All @@ -46,7 +47,7 @@ object TypeErasure {
case tp: TypeRef =>
val sym = tp.symbol
sym.isClass &&
!erasureDependsOnArgs(tp) &&
!erasureDependsOnArgs(sym) &&
!defn.erasedToObject.contains(sym) &&
!defn.isSyntheticFunctionClass(sym)
case _: TermRef =>
Expand Down Expand Up @@ -192,37 +193,48 @@ object TypeErasure {
}
}

/** Underlying type that does not contain aliases or abstract types
* at top-level, treating opaque aliases as transparent.
*/
def classify(tp: Type)(implicit ctx: Context): Type =
if (tp.typeSymbol.isClass) tp
else tp match {
case tp: TypeProxy => classify(tp.translucentSuperType)
case tp: AndOrType => tp.derivedAndOrType(classify(tp.tp1), classify(tp.tp2))
case _ => tp
}

/** Is `tp` an abstract type or polymorphic type parameter that has `Any`, `AnyVal`,
* or a universal trait as upper bound and that is not Java defined? Arrays of such types are
* erased to `Object` instead of `Object[]`.
*/
def isUnboundedGeneric(tp: Type)(implicit ctx: Context): Boolean = tp.dealias match {
case tp: TypeRef =>
case tp: TypeRef if !tp.symbol.isOpaqueHelper =>
!tp.symbol.isClass &&
!tp.derivesFrom(defn.ObjectClass) &&
!classify(tp).derivesFrom(defn.ObjectClass) &&
!tp.symbol.is(JavaDefined)
case tp: TypeParamRef =>
!tp.derivesFrom(defn.ObjectClass) &&
!classify(tp).derivesFrom(defn.ObjectClass) &&
!tp.binder.resultType.isJavaMethod
case tp: TypeAlias => isUnboundedGeneric(tp.alias)
case tp: TypeBounds => !tp.hi.derivesFrom(defn.ObjectClass)
case tp: TypeProxy => isUnboundedGeneric(tp.underlying)
case tp: TypeBounds => !classify(tp.hi).derivesFrom(defn.ObjectClass)
case tp: TypeProxy => isUnboundedGeneric(tp.translucentSuperType)
case tp: AndType => isUnboundedGeneric(tp.tp1) && isUnboundedGeneric(tp.tp2)
case tp: OrType => isUnboundedGeneric(tp.tp1) || isUnboundedGeneric(tp.tp2)
case _ => false
}

/** Is `tp` an abstract type or polymorphic type parameter, or another unbounded generic type? */
def isGeneric(tp: Type)(implicit ctx: Context): Boolean = tp.dealias match {
case tp: TypeRef => !tp.symbol.isClass
case tp: TypeRef if !tp.symbol.isOpaqueHelper => !tp.symbol.isClass
case tp: TypeParamRef => true
case tp: TypeProxy => isGeneric(tp.underlying)
case tp: TypeProxy => isGeneric(tp.translucentSuperType)
case tp: AndType => isGeneric(tp.tp1) || isGeneric(tp.tp2)
case tp: OrType => isGeneric(tp.tp1) || isGeneric(tp.tp2)
case _ => false
}

/** The erased least upper bound is computed as follows
/** The erased least upper bound of two erased types is computed as follows
* - if both argument are arrays of objects, an array of the erased lub of the element types
* - if both arguments are arrays of same primitives, an array of this primitive
* - if one argument is array of primitives and the other is array of objects, Object
Expand Down Expand Up @@ -286,7 +298,8 @@ object TypeErasure {
}
}

/** The erased greatest lower bound picks one of the two argument types. It prefers, in this order:
/** The erased greatest lower bound of two erased type picks one of the two argument types.
* It prefers, in this order:
* - arrays over non-arrays
* - subtypes over supertypes, unless isJava is set
* - real classes over traits
Expand Down Expand Up @@ -317,15 +330,15 @@ object TypeErasure {
* possible instantiations?
*/
def hasStableErasure(tp: Type)(implicit ctx: Context): Boolean = tp match {
case tp: TypeRef =>
case tp: TypeRef if !tp.symbol.isOpaqueHelper =>
tp.info match {
case TypeAlias(alias) => hasStableErasure(alias)
case _: ClassInfo => true
case _ => false
}
case tp: TypeParamRef => false
case tp: TypeBounds => false
case tp: TypeProxy => hasStableErasure(tp.superType)
case tp: TypeProxy => hasStableErasure(tp.translucentSuperType)
case tp: AndType => hasStableErasure(tp.tp1) && hasStableErasure(tp.tp2)
case tp: OrType => hasStableErasure(tp.tp1) && hasStableErasure(tp.tp2)
case _ => false
Expand Down Expand Up @@ -381,16 +394,17 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
tp
case tp: TypeRef =>
val sym = tp.symbol
if (!sym.isClass) this(tp.info)
if (!sym.isClass) this(tp.translucentSuperType)
else if (semiEraseVCs && isDerivedValueClass(sym)) eraseDerivedValueClassRef(tp)
else if (sym == defn.ArrayClass) apply(tp.appliedTo(TypeBounds.empty)) // i966 shows that we can hit a raw Array type.
else if (defn.isSyntheticFunctionClass(sym)) defn.erasedFunctionType(sym)
else eraseNormalClassRef(tp)
case tp: AppliedType =>
if (tp.tycon.isRef(defn.ArrayClass)) eraseArray(tp)
else if (tp.tycon.isRef(defn.PairClass)) erasePair(tp)
val tycon = tp.tycon
if (tycon.isRef(defn.ArrayClass)) eraseArray(tp)
else if (tycon.isRef(defn.PairClass)) erasePair(tp)
else if (tp.isRepeatedParam) apply(tp.underlyingIfRepeated(isJava))
else apply(tp.superType)
else apply(tp.translucentSuperType)
case _: TermRef | _: ThisType =>
this(tp.widen)
case SuperType(thistpe, supertpe) =>
Expand Down Expand Up @@ -419,7 +433,7 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
case tp @ ClassInfo(pre, cls, parents, decls, _) =>
if (cls is Package) tp
else {
def eraseParent(tp: Type) = tp.dealias match {
def eraseParent(tp: Type) = tp.dealias match { // note: can't be opaque, since it's a class parent
case tp: AppliedType if tp.tycon.isRef(defn.PairClass) => defn.ObjectType
case _ => apply(tp)
}
Expand All @@ -446,11 +460,9 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean

private def eraseArray(tp: Type)(implicit ctx: Context) = {
val defn.ArrayOf(elemtp) = tp
def arrayErasure(tpToErase: Type) =
erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(tpToErase)
if (elemtp derivesFrom defn.NullClass) JavaArrayType(defn.ObjectType)
if (classify(elemtp).derivesFrom(defn.NullClass)) JavaArrayType(defn.ObjectType)
else if (isUnboundedGeneric(elemtp) && !isJava) defn.ObjectType
else JavaArrayType(arrayErasure(elemtp))
else JavaArrayType(erasureFn(isJava, semiEraseVCs = false, isConstructor, wildcardOK)(elemtp))
}

private def erasePair(tp: Type)(implicit ctx: Context): Type = {
Expand Down Expand Up @@ -502,8 +514,10 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
// constructor method should not be semi-erased.
else if (isConstructor && isDerivedValueClass(sym)) eraseNormalClassRef(tp)
else this(tp)
case AppliedType(tycon, _) if tycon.typeSymbol.isClass && !erasureDependsOnArgs(tycon) =>
eraseResult(tycon)
case tp: AppliedType =>
val sym = tp.tycon.typeSymbol
if (sym.isClass && !erasureDependsOnArgs(sym)) eraseResult(tp.tycon)
else this(tp)
case _ =>
this(tp)
}
Expand All @@ -530,8 +544,8 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
}
val sym = tp.symbol
if (!sym.isClass) {
val info = tp.info
if (!info.exists) assert(false, "undefined: $tp with symbol $sym")
val info = tp.translucentSuperType
if (!info.exists) assert(false, i"undefined: $tp with symbol $sym")
return sigName(info)
}
if (isDerivedValueClass(sym)) {
Expand All @@ -543,10 +557,11 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
else
normalizeClass(sym.asClass).fullName.asTypeName
case tp: AppliedType =>
sigName(
if (erasureDependsOnArgs(tp.tycon)) this(tp)
else if (tp.tycon.typeSymbol.isClass) tp.underlying
else tp.superType)
val sym = tp.tycon.typeSymbol
sigName( // todo: what about repeatedParam?
if (erasureDependsOnArgs(sym)) this(tp)
else if (sym.isClass) tp.underlying
else tp.translucentSuperType)
case ErasedValueType(_, underlying) =>
sigName(underlying)
case JavaArrayType(elem) =>
Expand Down Expand Up @@ -574,6 +589,4 @@ class TypeErasure(isJava: Boolean, semiEraseVCs: Boolean, isConstructor: Boolean
println(s"no sig for $tp because of ${ex.printStackTrace()}")
throw ex
}


}
23 changes: 23 additions & 0 deletions compiler/src/dotty/tools/dotc/core/Types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1526,6 +1526,9 @@ object Types {
case TypeBounds(_, hi) => hi
case st => st
}

/** Same as superType, except that opaque types are treated as transparent aliases */
def translucentSuperType(implicit ctx: Context): Type = superType
}

// Every type has to inherit one of the following four abstract type classes.,
Expand Down Expand Up @@ -2214,6 +2217,14 @@ object Types {

override def underlying(implicit ctx: Context): Type = info

override def translucentSuperType(implicit ctx: Context) = info match {
case TypeAlias(aliased) => aliased
case TypeBounds(_, hi) =>
if (symbol.isOpaqueHelper) symbol.opaqueAlias.asSeenFrom(prefix, symbol.owner)
else hi
case _ => underlying
}

/** Hook that can be called from creation methods in TermRef and TypeRef */
def validated(implicit ctx: Context): this.type = {
this
Expand Down Expand Up @@ -2588,6 +2599,11 @@ object Types {
def isAnd: Boolean
def tp1: Type
def tp2: Type

def derivedAndOrType(tp1: Type, tp2: Type)(implicit ctx: Context) =
if ((tp1 eq this.tp1) && (tp2 eq this.tp2)) this
else if (isAnd) AndType.make(tp1, tp2, checkValid = true)
else OrType.make(tp1, tp2)
}

abstract case class AndType(tp1: Type, tp2: Type) extends AndOrType {
Expand Down Expand Up @@ -3351,6 +3367,13 @@ object Types {
cachedSuper
}

override def translucentSuperType(implicit ctx: Context): Type = tycon match {
case tycon: TypeRef if tycon.symbol.isOpaqueHelper =>
tycon.translucentSuperType.applyIfParameterized(args)
case _ =>
superType
}

override def tryNormalize(implicit ctx: Context): Type = tycon match {
case tycon: TypeRef =>
def tryMatchAlias = tycon.info match {
Expand Down