Fixed adriaan's patch for type constructor inference. The problem

with haranguing people in bars about bugs is that the fixes with
which they provide you may be flawed.  Fortunately moors has this
novelist on retainer.  Review by moors.

git-svn-id: http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk@25322 5e8d7ff9-d8ef-0310-90f0-a4852d11357a
This commit is contained in:
extempore 2011-07-18 21:05:05 +00:00
parent 28a1462f72
commit 1b97bc681a
3 changed files with 107 additions and 48 deletions

View File

@ -87,7 +87,7 @@ trait Types extends api.Types { self: SymbolTable =>
/** Decrement depth unless it is a don't care. */ /** Decrement depth unless it is a don't care. */
private final def decr(depth: Int) = if (depth == AnyDepth) AnyDepth else depth - 1 private final def decr(depth: Int) = if (depth == AnyDepth) AnyDepth else depth - 1
private final val printLubs = sys.props contains "scala.debug.lub" private final val printLubs = sys.props contains "scalac.debug.lub"
/** In case anyone wants to turn off lub verification without reverting anything. */ /** In case anyone wants to turn off lub verification without reverting anything. */
private final val verifyLubs = true private final val verifyLubs = true
@ -2390,13 +2390,20 @@ A type's typeSymbol should never be inspected directly.
new TypeVar(origin, constr, args, params) new TypeVar(origin, constr, args, params)
} }
/** A class representing a type variable /** A class representing a type variable: not used after phase `typer`.
* Not used after phase `typer`. *
* A higher-kinded type variable has type arguments (a list of Type's) and type parameters (list of Symbols) * A higher-kinded TypeVar has params (Symbols) and typeArgs (Types).
* A TypeVar whose list of args is non-empty can only be instantiated by a higher-kinded type that can be applied to these args * A TypeVar with nonEmpty typeArgs can only be instantiated by a higher-kinded
* a typevar is much like a typeref, except it has special logic for type equality/subtyping * type that can be applied to those args. A TypeVar is much like a TypeRef,
* except it has special logic for equality and subtyping.
*/ */
class TypeVar(val origin: Type, val constr0: TypeConstraint, override val typeArgs: List[Type], override val params: List[Symbol]) extends Type { class TypeVar(
val origin: Type,
val constr0: TypeConstraint,
override val typeArgs: List[Type],
override val params: List[Symbol]
) extends Type {
private val numArgs = typeArgs.length
// params are needed to keep track of variance (see mapOverArgs in SubstMap) // params are needed to keep track of variance (see mapOverArgs in SubstMap)
assert(typeArgs.isEmpty || sameLength(typeArgs, params)) assert(typeArgs.isEmpty || sameLength(typeArgs, params))
// var tid = { tidCount += 1; tidCount } //DEBUG // var tid = { tidCount += 1; tidCount } //DEBUG
@ -2409,14 +2416,16 @@ A type's typeSymbol should never be inspected directly.
val level = skolemizationLevel val level = skolemizationLevel
/** Two occurrences of a higher-kinded typevar, e.g. `?CC[Int]` and `?CC[String]`, correspond to /** Two occurrences of a higher-kinded typevar, e.g. `?CC[Int]` and `?CC[String]`, correspond to
* ''two instances'' of `TypeVar` that share the ''same'' `TypeConstraint` * ''two instances'' of `TypeVar` that share the ''same'' `TypeConstraint`.
* `constr` for `?CC` only tracks type constructors anyway, so when `?CC[Int] <:< List[Int]` and `?CC[String] <:< Iterable[String]` *
* `?CC's` hibounds contains List and Iterable * `constr` for `?CC` only tracks type constructors anyway,
* so when `?CC[Int] <:< List[Int]` and `?CC[String] <:< Iterable[String]`
* `?CC's` hibounds contains List and Iterable.
*/ */
def applyArgs(newArgs: List[Type]): TypeVar = def applyArgs(newArgs: List[Type]): TypeVar =
if (newArgs.isEmpty) this // SubstMap relies on this (though this check is redundant when called from appliedType...) if (newArgs.isEmpty) this // SubstMap relies on this (though this check is redundant when called from appliedType...)
else TypeVar(origin, constr, newArgs, params) // @M TODO: interaction with undoLog?? else TypeVar(origin, constr, newArgs, params) // @M TODO: interaction with undoLog??
// newArgs.length may differ from args.length (could've been empty before) // newArgs.length may differ from args.length (could've been empty before)
// example: when making new typevars, you start out with C[A], then you replace C by ?C, which should yield ?C[A], then A by ?A, ?C[?A] // example: when making new typevars, you start out with C[A], then you replace C by ?C, which should yield ?C[A], then A by ?A, ?C[?A]
// we need to track a TypeVar's arguments, and map over them (see TypeMap::mapOver) // we need to track a TypeVar's arguments, and map over them (see TypeMap::mapOver)
// TypeVars get applied to different arguments over time (in asSeenFrom) // TypeVars get applied to different arguments over time (in asSeenFrom)
@ -2427,7 +2436,8 @@ A type's typeSymbol should never be inspected directly.
// they share the same TypeConstraint instance // they share the same TypeConstraint instance
// <region name="constraint mutators + undoLog"> // <region name="constraint mutators + undoLog">
// invariant: before mutating constr, save old state in undoLog (undoLog is used to reset constraints to avoid piling up unrelated ones) // invariant: before mutating constr, save old state in undoLog
// (undoLog is used to reset constraints to avoid piling up unrelated ones)
def setInst(tp: Type) { def setInst(tp: Type) {
// assert(!(tp containsTp this), this) // assert(!(tp containsTp this), this)
undoLog record this undoLog record this
@ -2435,7 +2445,7 @@ A type's typeSymbol should never be inspected directly.
} }
def addLoBound(tp: Type, isNumericBound: Boolean = false) { def addLoBound(tp: Type, isNumericBound: Boolean = false) {
assert(tp != this) // implies there is a cycle somewhere (?) assert(tp != this, tp) // implies there is a cycle somewhere (?)
//println("addLoBound: "+(safeToString, debugString(tp))) //DEBUG //println("addLoBound: "+(safeToString, debugString(tp))) //DEBUG
undoLog record this undoLog record this
constr.addLoBound(tp, isNumericBound) constr.addLoBound(tp, isNumericBound)
@ -2463,22 +2473,21 @@ A type's typeSymbol should never be inspected directly.
*/ */
def registerBound(tp: Type, isLowerBound: Boolean, isNumericBound: Boolean = false): Boolean = { def registerBound(tp: Type, isLowerBound: Boolean, isNumericBound: Boolean = false): Boolean = {
// println("regBound: "+(safeToString, debugString(tp), isLowerBound)) //@MDEBUG // println("regBound: "+(safeToString, debugString(tp), isLowerBound)) //@MDEBUG
if (isLowerBound) assert(tp != this) if (isLowerBound)
assert(tp != this)
def checkSubtypeLower(tp1: Type, tp2: Type) = // side effect: adds the type to upper or lower bounds
if (isNumericBound) tp1 weak_<:< tp2 def addBound(tp: Type) {
else tp1 <:< tp2
// swaps the arguments if it's an upper bound
def checkSubtype(tp1: Type, tp2: Type) =
if (isLowerBound) checkSubtypeLower(tp1, tp2)
else checkSubtypeLower(tp2, tp1)
def addBound(tp: Type) = {
if (isLowerBound) addLoBound(tp, isNumericBound) if (isLowerBound) addLoBound(tp, isNumericBound)
else addHiBound(tp, isNumericBound) else addHiBound(tp, isNumericBound)
// println("addedBound: "+(this, tp)) // @MDEBUG }
true // swaps the arguments if it's an upper bound
def checkSubtype(tp1: Type, tp2: Type) = {
val lhs = if (isLowerBound) tp1 else tp2
val rhs = if (isLowerBound) tp2 else tp1
if (isNumericBound) lhs weak_<:< rhs
else lhs <:< rhs
} }
/** Simple case: type arguments can be ignored, because either this typevar has /** Simple case: type arguments can be ignored, because either this typevar has
@ -2494,8 +2503,12 @@ A type's typeSymbol should never be inspected directly.
* ?TC[?T] <: Any * ?TC[?T] <: Any
* }}} * }}}
*/ */
def unifySimple = (params.isEmpty || tp.typeSymbol == NothingClass || tp.typeSymbol == AnyClass) && def unifySimple = (
addBound(tp) (params.isEmpty || tp.typeSymbol == NothingClass || tp.typeSymbol == AnyClass) && {
addBound(tp)
true
}
)
/** Full case: involving a check of the form /** Full case: involving a check of the form
* {{{ * {{{
@ -2504,27 +2517,67 @@ A type's typeSymbol should never be inspected directly.
* Checks subtyping of higher-order type vars, and uses variances as defined in the * Checks subtyping of higher-order type vars, and uses variances as defined in the
* type parameter we're trying to infer (the result will be sanity-checked later). * type parameter we're trying to infer (the result will be sanity-checked later).
*/ */
def unifyFull(tp: Type) = sameLength(typeArgs, tp.typeArgs) && { // this is a higher-kinded type var with same arity as tp def unifyFull(tpe: Type) = {
// side effect: adds the type constructor itself as a bound // Since the alias/widen variations are often no-ops, this
addBound(tp.typeConstructor) // keenly collects them in a Set to avoid redundant tests.
if (isLowerBound) isSubArgs(tp.typeArgs, typeArgs, params) val tpes = (
else isSubArgs(typeArgs, tp.typeArgs, params) if (isLowerBound) Set(tpe, tpe.widen, tpe.dealias, tpe.widen.dealias)
else Set(tpe)
)
tpes exists { tp =>
val lhs = if (isLowerBound) tp.typeArgs else typeArgs
val rhs = if (isLowerBound) typeArgs else tp.typeArgs
sameLength(lhs, rhs) && {
// this is a higher-kinded type var with same arity as tp.
// side effect: adds the type constructor itself as a bound
addBound(tp.typeConstructor)
isSubArgs(lhs, rhs, params)
}
}
} }
/** TODO: need positive/negative test cases demonstrating this is correct. */ // There's a <: test taking place right now, where tp is a concrete type and this is a typevar
def unifyParents = // attempting to satisfy that test. Either the test will be unsatisfiable, in which case
if (isLowerBound) tp.parents exists unifyFull // registerBound will return false; or the upper or lower bounds of this type var will be
else tp.parents forall unifyFull // supplemented with the type being tested against.
//
// TODO: fancier unification, maybe rewrite constraint as follows? // Eventually the types which have accumulated in the upper and lower bounds will be lubbed
// val sym = constr.hiBounds map {_.typeSymbol} find { _.typeParams.length == typeArgs.length} // (resp. glbbed) to instantiate the typevar.
// this <: tp.baseType(sym) //
if (suspended) checkSubtype(tp, origin) // The only types which are eligible for unification are those with the same number of
else if (constr.instValid) checkSubtype(tp, constr.inst) // type var is already set // typeArgs as this typevar, or Any/Nothing, which are kind-polymorphic. For the upper bound,
else isRelatable(tp) && { // gradually let go of some type precision in hopes of finding a type that has the same shape as the type variable // any parent or base type of `tp` may be tested here (leading to a corresponding relaxation
// okay, this just screams "CLEAN ME UP" -- I think we could use tp.widen instead of tp straight from the get-go in registerBound, since we don't infer singleton types anyway (but maybe that'll change?) // in the upper bound.) The universe of possible glbs, being somewhat more infinite, is not
unifySimple || unifyFull(tp) || unifyFull(tp.dealias) || unifyFull(tp.widen) || unifyFull(tp.widen.dealias) || unifyParents // addressed here: all lower bounds are retained and their intersection calculated when the
} // bounds are solved.
//
// In a side-effect free universe, checking tp and tp.parents beofre checking tp.baseTypeSeq
// would be pointless. In this case, each check we perform causes us to lose specificity: in
// the end the best we'll do is the least specific type we tested against, since the typevar
// does not see these checks as "probes" but as requirements to fulfill.
//
// So the strategy used here is to test first the type, then the direct parents, and finally
// to fall back on the individual base types. This warrants eventual re-examination.
if (suspended) // constraint accumulation is disabled
checkSubtype(tp, origin)
else if (constr.instValid) // type var is already set
checkSubtype(tp, constr.inst)
else
// isRelatable checks for type skolems which cannot be understood at this level
isRelatable(tp) && (
unifySimple || unifyFull(tp) || (
// only look harder if our gaze is oriented toward Any
isLowerBound && (
(tp.parents exists unifyFull) || (
// @PP: Is it going to be faster to filter out the parents we just checked?
// That's what's done here but I'm not sure it matters.
tp.baseTypeSeq.toList.tail filterNot (tp.parents contains _) exists unifyFull
)
)
)
)
} }
def registerTypeEquality(tp: Type, typeVarLHS: Boolean): Boolean = { def registerTypeEquality(tp: Type, typeVarLHS: Boolean): Boolean = {

View File

@ -281,9 +281,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb
def profileMem = settings.YprofileMem.value def profileMem = settings.YprofileMem.value
// shortish-term property based options // shortish-term property based options
def timings = sys.props contains "scala.timings" def timings = (sys.props contains "scala.timings")
def inferDebug = (sys.props contains "scalac.debug.infer") || settings.Yinferdebug.value def inferDebug = (sys.props contains "scalac.debug.infer") || settings.Yinferdebug.value
def typerDebug = (sys.props contains "scalac.debug.typer") || settings.Ytyperdebug.value def typerDebug = (sys.props contains "scalac.debug.typer") || settings.Ytyperdebug.value
def lubDebug = (sys.props contains "scalac.debug.lub")
} }
// True if -Xscript has been set, indicating a script run. // True if -Xscript has been set, indicating a script run.

View File

@ -0,0 +1,5 @@
class A {
def f[CC[X] <: Traversable[X]](x: CC[Int]) = ()
f(1 to 5)
}