Skip to content

Commit 658ba1b

Browse files
committed
Fixed adriaan's patch for type constructor infe...
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.
1 parent b2a1ced commit 658ba1b

File tree

3 files changed

+107
-48
lines changed

3 files changed

+107
-48
lines changed

src/compiler/scala/reflect/internal/Types.scala

Lines changed: 100 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ trait Types extends api.Types { self: SymbolTable =>
8787
/** Decrement depth unless it is a don't care. */
8888
private final def decr(depth: Int) = if (depth == AnyDepth) AnyDepth else depth - 1
8989

90-
private final val printLubs = sys.props contains "scala.debug.lub"
90+
private final val printLubs = sys.props contains "scalac.debug.lub"
9191
/** In case anyone wants to turn off lub verification without reverting anything. */
9292
private final val verifyLubs = true
9393

@@ -2390,13 +2390,20 @@ A type's typeSymbol should never be inspected directly.
23902390
new TypeVar(origin, constr, args, params)
23912391
}
23922392

2393-
/** A class representing a type variable
2394-
* Not used after phase `typer`.
2395-
* A higher-kinded type variable has type arguments (a list of Type's) and type parameters (list of Symbols)
2396-
* 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
2397-
* a typevar is much like a typeref, except it has special logic for type equality/subtyping
2393+
/** A class representing a type variable: not used after phase `typer`.
2394+
*
2395+
* A higher-kinded TypeVar has params (Symbols) and typeArgs (Types).
2396+
* A TypeVar with nonEmpty typeArgs can only be instantiated by a higher-kinded
2397+
* type that can be applied to those args. A TypeVar is much like a TypeRef,
2398+
* except it has special logic for equality and subtyping.
23982399
*/
2399-
class TypeVar(val origin: Type, val constr0: TypeConstraint, override val typeArgs: List[Type], override val params: List[Symbol]) extends Type {
2400+
class TypeVar(
2401+
val origin: Type,
2402+
val constr0: TypeConstraint,
2403+
override val typeArgs: List[Type],
2404+
override val params: List[Symbol]
2405+
) extends Type {
2406+
private val numArgs = typeArgs.length
24002407
// params are needed to keep track of variance (see mapOverArgs in SubstMap)
24012408
assert(typeArgs.isEmpty || sameLength(typeArgs, params))
24022409
// var tid = { tidCount += 1; tidCount } //DEBUG
@@ -2409,14 +2416,16 @@ A type's typeSymbol should never be inspected directly.
24092416
val level = skolemizationLevel
24102417

24112418
/** Two occurrences of a higher-kinded typevar, e.g. `?CC[Int]` and `?CC[String]`, correspond to
2412-
* ''two instances'' of `TypeVar` that share the ''same'' `TypeConstraint`
2413-
* `constr` for `?CC` only tracks type constructors anyway, so when `?CC[Int] <:< List[Int]` and `?CC[String] <:< Iterable[String]`
2414-
* `?CC's` hibounds contains List and Iterable
2419+
* ''two instances'' of `TypeVar` that share the ''same'' `TypeConstraint`.
2420+
*
2421+
* `constr` for `?CC` only tracks type constructors anyway,
2422+
* so when `?CC[Int] <:< List[Int]` and `?CC[String] <:< Iterable[String]`
2423+
* `?CC's` hibounds contains List and Iterable.
24152424
*/
24162425
def applyArgs(newArgs: List[Type]): TypeVar =
24172426
if (newArgs.isEmpty) this // SubstMap relies on this (though this check is redundant when called from appliedType...)
24182427
else TypeVar(origin, constr, newArgs, params) // @M TODO: interaction with undoLog??
2419-
// newArgs.length may differ from args.length (could've been empty before)
2428+
// newArgs.length may differ from args.length (could've been empty before)
24202429
// 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]
24212430
// we need to track a TypeVar's arguments, and map over them (see TypeMap::mapOver)
24222431
// TypeVars get applied to different arguments over time (in asSeenFrom)
@@ -2427,15 +2436,16 @@ A type's typeSymbol should never be inspected directly.
24272436
// they share the same TypeConstraint instance
24282437

24292438
// <region name="constraint mutators + undoLog">
2430-
// invariant: before mutating constr, save old state in undoLog (undoLog is used to reset constraints to avoid piling up unrelated ones)
2439+
// invariant: before mutating constr, save old state in undoLog
2440+
// (undoLog is used to reset constraints to avoid piling up unrelated ones)
24312441
def setInst(tp: Type) {
24322442
// assert(!(tp containsTp this), this)
24332443
undoLog record this
24342444
constr.inst = tp
24352445
}
24362446

24372447
def addLoBound(tp: Type, isNumericBound: Boolean = false) {
2438-
assert(tp != this) // implies there is a cycle somewhere (?)
2448+
assert(tp != this, tp) // implies there is a cycle somewhere (?)
24392449
//println("addLoBound: "+(safeToString, debugString(tp))) //DEBUG
24402450
undoLog record this
24412451
constr.addLoBound(tp, isNumericBound)
@@ -2463,22 +2473,21 @@ A type's typeSymbol should never be inspected directly.
24632473
*/
24642474
def registerBound(tp: Type, isLowerBound: Boolean, isNumericBound: Boolean = false): Boolean = {
24652475
// println("regBound: "+(safeToString, debugString(tp), isLowerBound)) //@MDEBUG
2466-
if (isLowerBound) assert(tp != this)
2467-
2468-
def checkSubtypeLower(tp1: Type, tp2: Type) =
2469-
if (isNumericBound) tp1 weak_<:< tp2
2470-
else tp1 <:< tp2
2476+
if (isLowerBound)
2477+
assert(tp != this)
24712478

2472-
// swaps the arguments if it's an upper bound
2473-
def checkSubtype(tp1: Type, tp2: Type) =
2474-
if (isLowerBound) checkSubtypeLower(tp1, tp2)
2475-
else checkSubtypeLower(tp2, tp1)
2476-
2477-
def addBound(tp: Type) = {
2479+
// side effect: adds the type to upper or lower bounds
2480+
def addBound(tp: Type) {
24782481
if (isLowerBound) addLoBound(tp, isNumericBound)
24792482
else addHiBound(tp, isNumericBound)
2480-
// println("addedBound: "+(this, tp)) // @MDEBUG
2481-
true
2483+
}
2484+
// swaps the arguments if it's an upper bound
2485+
def checkSubtype(tp1: Type, tp2: Type) = {
2486+
val lhs = if (isLowerBound) tp1 else tp2
2487+
val rhs = if (isLowerBound) tp2 else tp1
2488+
2489+
if (isNumericBound) lhs weak_<:< rhs
2490+
else lhs <:< rhs
24822491
}
24832492

24842493
/** 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.
24942503
* ?TC[?T] <: Any
24952504
* }}}
24962505
*/
2497-
def unifySimple = (params.isEmpty || tp.typeSymbol == NothingClass || tp.typeSymbol == AnyClass) &&
2498-
addBound(tp)
2506+
def unifySimple = (
2507+
(params.isEmpty || tp.typeSymbol == NothingClass || tp.typeSymbol == AnyClass) && {
2508+
addBound(tp)
2509+
true
2510+
}
2511+
)
24992512

25002513
/** Full case: involving a check of the form
25012514
* {{{
@@ -2504,27 +2517,67 @@ A type's typeSymbol should never be inspected directly.
25042517
* Checks subtyping of higher-order type vars, and uses variances as defined in the
25052518
* type parameter we're trying to infer (the result will be sanity-checked later).
25062519
*/
2507-
def unifyFull(tp: Type) = sameLength(typeArgs, tp.typeArgs) && { // this is a higher-kinded type var with same arity as tp
2508-
// side effect: adds the type constructor itself as a bound
2509-
addBound(tp.typeConstructor)
2510-
if (isLowerBound) isSubArgs(tp.typeArgs, typeArgs, params)
2511-
else isSubArgs(typeArgs, tp.typeArgs, params)
2520+
def unifyFull(tpe: Type) = {
2521+
// Since the alias/widen variations are often no-ops, this
2522+
// keenly collects them in a Set to avoid redundant tests.
2523+
val tpes = (
2524+
if (isLowerBound) Set(tpe, tpe.widen, tpe.dealias, tpe.widen.dealias)
2525+
else Set(tpe)
2526+
)
2527+
tpes exists { tp =>
2528+
val lhs = if (isLowerBound) tp.typeArgs else typeArgs
2529+
val rhs = if (isLowerBound) typeArgs else tp.typeArgs
2530+
2531+
sameLength(lhs, rhs) && {
2532+
// this is a higher-kinded type var with same arity as tp.
2533+
// side effect: adds the type constructor itself as a bound
2534+
addBound(tp.typeConstructor)
2535+
isSubArgs(lhs, rhs, params)
2536+
}
2537+
}
25122538
}
25132539

2514-
/** TODO: need positive/negative test cases demonstrating this is correct. */
2515-
def unifyParents =
2516-
if (isLowerBound) tp.parents exists unifyFull
2517-
else tp.parents forall unifyFull
2518-
2519-
// TODO: fancier unification, maybe rewrite constraint as follows?
2520-
// val sym = constr.hiBounds map {_.typeSymbol} find { _.typeParams.length == typeArgs.length}
2521-
// this <: tp.baseType(sym)
2522-
if (suspended) checkSubtype(tp, origin)
2523-
else if (constr.instValid) checkSubtype(tp, constr.inst) // type var is already set
2524-
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
2525-
// 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?)
2526-
unifySimple || unifyFull(tp) || unifyFull(tp.dealias) || unifyFull(tp.widen) || unifyFull(tp.widen.dealias) || unifyParents
2527-
}
2540+
// There's a <: test taking place right now, where tp is a concrete type and this is a typevar
2541+
// attempting to satisfy that test. Either the test will be unsatisfiable, in which case
2542+
// registerBound will return false; or the upper or lower bounds of this type var will be
2543+
// supplemented with the type being tested against.
2544+
//
2545+
// Eventually the types which have accumulated in the upper and lower bounds will be lubbed
2546+
// (resp. glbbed) to instantiate the typevar.
2547+
//
2548+
// The only types which are eligible for unification are those with the same number of
2549+
// typeArgs as this typevar, or Any/Nothing, which are kind-polymorphic. For the upper bound,
2550+
// any parent or base type of `tp` may be tested here (leading to a corresponding relaxation
2551+
// in the upper bound.) The universe of possible glbs, being somewhat more infinite, is not
2552+
// addressed here: all lower bounds are retained and their intersection calculated when the
2553+
// bounds are solved.
2554+
//
2555+
// In a side-effect free universe, checking tp and tp.parents beofre checking tp.baseTypeSeq
2556+
// would be pointless. In this case, each check we perform causes us to lose specificity: in
2557+
// the end the best we'll do is the least specific type we tested against, since the typevar
2558+
// does not see these checks as "probes" but as requirements to fulfill.
2559+
//
2560+
// So the strategy used here is to test first the type, then the direct parents, and finally
2561+
// to fall back on the individual base types. This warrants eventual re-examination.
2562+
2563+
if (suspended) // constraint accumulation is disabled
2564+
checkSubtype(tp, origin)
2565+
else if (constr.instValid) // type var is already set
2566+
checkSubtype(tp, constr.inst)
2567+
else
2568+
// isRelatable checks for type skolems which cannot be understood at this level
2569+
isRelatable(tp) && (
2570+
unifySimple || unifyFull(tp) || (
2571+
// only look harder if our gaze is oriented toward Any
2572+
isLowerBound && (
2573+
(tp.parents exists unifyFull) || (
2574+
// @PP: Is it going to be faster to filter out the parents we just checked?
2575+
// That's what's done here but I'm not sure it matters.
2576+
tp.baseTypeSeq.toList.tail filterNot (tp.parents contains _) exists unifyFull
2577+
)
2578+
)
2579+
)
2580+
)
25282581
}
25292582

25302583
def registerTypeEquality(tp: Type, typeVarLHS: Boolean): Boolean = {

src/compiler/scala/tools/nsc/Global.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,9 +281,10 @@ class Global(var currentSettings: Settings, var reporter: Reporter) extends Symb
281281
def profileMem = settings.YprofileMem.value
282282

283283
// shortish-term property based options
284-
def timings = sys.props contains "scala.timings"
284+
def timings = (sys.props contains "scala.timings")
285285
def inferDebug = (sys.props contains "scalac.debug.infer") || settings.Yinferdebug.value
286286
def typerDebug = (sys.props contains "scalac.debug.typer") || settings.Ytyperdebug.value
287+
def lubDebug = (sys.props contains "scalac.debug.lub")
287288
}
288289

289290
// True if -Xscript has been set, indicating a script run.

test/files/pos/hkrange.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class A {
2+
def f[CC[X] <: Traversable[X]](x: CC[Int]) = ()
3+
4+
f(1 to 5)
5+
}

0 commit comments

Comments
 (0)