Skip to content
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

Improvements to cyclic checking, avoidance, named parameters #1186

Merged
merged 18 commits into from
Apr 6, 2016
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
15 changes: 15 additions & 0 deletions src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1069,6 +1069,9 @@ object SymDenotations {
/** The type parameters of a class symbol, Nil for all other symbols */
def typeParams(implicit ctx: Context): List[TypeSymbol] = Nil

/** The named type parameters declared or inherited by this symbol */
def namedTypeParams(implicit ctx: Context): Set[TypeSymbol] = Set()

/** The type This(cls), where cls is this class, NoPrefix for all other symbols */
def thisType(implicit ctx: Context): Type = NoPrefix

Expand Down Expand Up @@ -1202,6 +1205,8 @@ object SymDenotations {
/** TODO: Document why caches are supposedly safe to use */
private[this] var myTypeParams: List[TypeSymbol] = _

private[this] var myNamedTypeParams: Set[TypeSymbol] = _

/** The type parameters of this class */
override final def typeParams(implicit ctx: Context): List[TypeSymbol] = {
def computeTypeParams = {
Expand All @@ -1214,6 +1219,16 @@ object SymDenotations {
myTypeParams
}

/** The named type parameters declared or inherited by this class */
override final def namedTypeParams(implicit ctx: Context): Set[TypeSymbol] = {
def computeNamedTypeParams: Set[TypeSymbol] =
if (ctx.erasedTypes || is(Module)) Set() // fast return for modules to avoid scanning package decls
else memberNames(abstractTypeNameFilter).map(name =>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we create a NameFilter instance that filters named type params directly instead?

  object namedTypeParamNameFilter extends NameFilter {
    def apply(pre: Type, name: Name)(implicit ctx: Context): Boolean =
      name.isTypeName && {
        val mbr = pre.member(name)
        mbr.symbol.is(Deferred | TypeParam, butNot = ExpandedName) &&
        mbr.info.isInstanceOf[RealTypeBounds]
      }
  }

info.member(name).symbol.asType).filter(_.is(TypeParam, butNot = ExpandedName)).toSet
if (myNamedTypeParams == null) myNamedTypeParams = computeNamedTypeParams
myNamedTypeParams
}

override protected[dotc] final def info_=(tp: Type) = {
super.info_=(tp)
myTypeParams = null // changing the info might change decls, and with it typeParams
Expand Down
61 changes: 61 additions & 0 deletions src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,67 @@ class TypeApplications(val self: Type) extends AnyVal {
}
}

/** The named type parameters declared or inherited by this type.
* These are all uninstantiated named type parameters of this type or one
* of its base types.
*/
final def namedTypeParams(implicit ctx: Context): Set[TypeSymbol] = self match {
case self: ClassInfo =>
self.cls.namedTypeParams
case self: RefinedType =>
self.parent.namedTypeParams.filterNot(_.name == self.refinedName)
case self: SingletonType =>
Set()
case self: TypeProxy =>
self.underlying.namedTypeParams
case _ =>
Set()
}

/** The smallest supertype of this type that instantiated none of the named type parameters
* in `params`. That is, for each named type parameter `p` in `params`, either there is
* no type field named `p` in this type, or `p` is a named type parameter of this type.
* The first case is important for the recursive case of AndTypes, because some of their operands might
* be missing the named parameter altogether, but the AndType as a whole can still
* contain it.
*/
final def widenToNamedTypeParams(params: Set[TypeSymbol])(implicit ctx: Context): Type = {

/** Is widening not needed for `tp`? */
def isOK(tp: Type) = {
val ownParams = tp.namedTypeParams
def isMissingOrOpen(param: TypeSymbol) = {
val ownParam = tp.nonPrivateMember(param.name).symbol
!ownParam.exists || ownParams.contains(ownParam.asType)
}
params.forall(isMissingOrOpen)
}

/** Widen type by forming the intersection of its widened parents */
def widenToParents(tp: Type) = {
val parents = tp.parents.map(p =>
tp.baseTypeWithArgs(p.symbol).widenToNamedTypeParams(params))
parents.reduceLeft(ctx.typeComparer.andType(_, _))
}

if (isOK(self)) self
else self match {
case self @ AppliedType(tycon, args) if !isOK(tycon) =>
widenToParents(self)
case self: TypeRef if self.symbol.isClass =>
widenToParents(self)
case self: RefinedType =>
val parent1 = self.parent.widenToNamedTypeParams(params)
if (params.exists(_.name == self.refinedName)) parent1
else self.derivedRefinedType(parent1, self.refinedName, self.refinedInfo)
case self: TypeProxy =>
self.underlying.widenToNamedTypeParams(params)
case self: AndOrType =>
self.derivedAndOrType(
self.tp1.widenToNamedTypeParams(params), self.tp2.widenToNamedTypeParams(params))
}
}

/** The Lambda trait underlying a type lambda */
def LambdaTrait(implicit ctx: Context): Symbol = self.stripTypeVar match {
case RefinedType(parent, tpnme.hkApply) =>
Expand Down
18 changes: 16 additions & 2 deletions src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1075,15 +1075,29 @@ class TypeComparer(initctx: Context) extends DotClass with ConstraintHandling {
}
}

/** `op(tp1, tp2)` unless `tp1` and `tp2` are type-constructors.
/** `op(tp1, tp2)` unless `tp1` and `tp2` are type-constructors with at least
* some unnamed type parameters.
* In the latter case, combine `tp1` and `tp2` under a type lambda like this:
*
* [X1, ..., Xn] -> op(tp1[X1, ..., Xn], tp2[X1, ..., Xn])
*
* Note: There is a tension between named and positional parameters here, which
* is impossible to resolve completely. Say you have
*
* C[type T], D[type U]
*
* Then do you expand `C & D` to `[T] -> C[T] & D[T]` or not? Under the named
* type parameter interpretation, this would be wrong whereas under the traditional
* higher-kinded interpretation this would be required. The problem arises from
* allowing both interpretations. A possible remedy is to be somehow stricter
* in where we allow which interpretation.
*/
private def liftIfHK(tp1: Type, tp2: Type, op: (Type, Type) => Type) = {
val tparams1 = tp1.typeParams
val tparams2 = tp2.typeParams
if (tparams1.isEmpty || tparams2.isEmpty) op(tp1, tp2)
def onlyNamed(tparams: List[TypeSymbol]) = tparams.forall(!_.is(ExpandedName))
if (tparams1.isEmpty || tparams2.isEmpty ||
onlyNamed(tparams1) && onlyNamed(tparams2)) op(tp1, tp2)
else if (tparams1.length != tparams2.length) mergeConflict(tp1, tp2)
else hkCombine(tp1, tp2, tparams1, tparams2, op)
}
Expand Down
97 changes: 9 additions & 88 deletions src/dotty/tools/dotc/core/TypeOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -122,100 +122,21 @@ trait TypeOps { this: Context => // TODO: Make standalone object.
def currentVariance = variance
}

/** Approximate a type `tp` with a type that does not contain skolem types.
*/
final def deskolemize(tp: Type): Type = deskolemize(tp, 1, Set())

private def deskolemize(tp: Type, variance: Int, seen: Set[SkolemType]): Type = {
def approx(lo: Type = defn.NothingType, hi: Type = defn.AnyType, newSeen: Set[SkolemType] = seen) =
if (variance == 0) NoType
else deskolemize(if (variance < 0) lo else hi, variance, newSeen)
tp match {
/** Approximate a type `tp` with a type that does not contain skolem types. */
object deskolemize extends ApproximatingTypeMap {
private var seen: Set[SkolemType] = Set()
def apply(tp: Type) = tp match {
case tp: SkolemType =>
if (seen contains tp) NoType
else approx(hi = tp.info, newSeen = seen + tp)
case tp: NamedType =>
val sym = tp.symbol
if (sym.isStatic) tp
else {
val pre1 = deskolemize(tp.prefix, variance, seen)
if (pre1 eq tp.prefix) tp
else {
val d = tp.prefix.member(tp.name)
d.info match {
case TypeAlias(alias) => deskolemize(alias, variance, seen)
case _ =>
if (pre1.exists && !pre1.isRef(defn.NothingClass)) tp.derivedSelect(pre1)
else {
ctx.log(s"deskolem: $tp: ${tp.info}")
tp.info match {
case TypeBounds(lo, hi) => approx(lo, hi)
case info => approx(defn.NothingType, info)
}
}
}
}
val saved = seen
seen += tp
try approx(hi = tp.info)
finally seen = saved
}
case _: ThisType | _: BoundType | _: SuperType | NoType | NoPrefix =>
tp
case tp: RefinedType =>
val parent1 = deskolemize(tp.parent, variance, seen)
if (parent1.exists) {
val refinedInfo1 = deskolemize(tp.refinedInfo, variance, seen)
if (refinedInfo1.exists)
tp.derivedRefinedType(parent1, tp.refinedName, refinedInfo1)
else
approx(hi = parent1)
}
else approx()
case tp: TypeAlias =>
val alias1 = deskolemize(tp.alias, variance * tp.variance, seen)
if (alias1.exists) tp.derivedTypeAlias(alias1)
else approx(hi = TypeBounds.empty)
case tp: TypeBounds =>
val lo1 = deskolemize(tp.lo, -variance, seen)
val hi1 = deskolemize(tp.hi, variance, seen)
if (lo1.exists && hi1.exists) tp.derivedTypeBounds(lo1, hi1)
else approx(hi =
if (lo1.exists) TypeBounds.lower(lo1)
else if (hi1.exists) TypeBounds.upper(hi1)
else TypeBounds.empty)
case tp: ClassInfo =>
val pre1 = deskolemize(tp.prefix, variance, seen)
if (pre1.exists) tp.derivedClassInfo(pre1)
else NoType
case tp: AndOrType =>
val tp1d = deskolemize(tp.tp1, variance, seen)
val tp2d = deskolemize(tp.tp2, variance, seen)
if (tp1d.exists && tp2d.exists)
tp.derivedAndOrType(tp1d, tp2d)
else if (tp.isAnd)
approx(hi = tp1d & tp2d) // if one of tp1d, tp2d exists, it is the result of tp1d & tp2d
else
approx(lo = tp1d & tp2d)
case tp: WildcardType =>
val bounds1 = deskolemize(tp.optBounds, variance, seen)
if (bounds1.exists) tp.derivedWildcardType(bounds1)
else WildcardType
case _ =>
if (tp.isInstanceOf[MethodicType]) assert(variance != 0, tp)
deskolemizeMap.mapOver(tp, variance, seen)
}
}

object deskolemizeMap extends TypeMap {
private var seen: Set[SkolemType] = _
def apply(tp: Type) = deskolemize(tp, variance, seen)
def mapOver(tp: Type, variance: Int, seen: Set[SkolemType]) = {
val savedVariance = this.variance
val savedSeen = this.seen
this.variance = variance
this.seen = seen
try super.mapOver(tp)
finally {
this.variance = savedVariance
this.seen = savedSeen
}
mapOver(tp)
}
}

Expand Down
Loading