Improvements to cyclic checking, avoidance, named parameters #1186

Merged
merged 18 commits into from Apr 6, 2016
@@ -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
@@ -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 = {
@@ -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 =>

This comment has been minimized.

@smarter

smarter Apr 6, 2016

Member

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]
      }
  }
@smarter

smarter Apr 6, 2016

Member

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
@@ -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) =>
@@ -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)
}
@@ -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)
}
}
Oops, something went wrong.