Skip to content

Commit c36ce49

Browse files
author
extempore
committed
Fixed a big bug in type constructor unification caused by
considering only the parents rather than all the base types. This fix is a testament to the power of haranguing people in bars when you are deeply offended by a bug, like someone was by this one: def f[CC[X] <: Traversable[X]](x: CC[Int]) = () f(1 to 5) // did not compile! Fear not, it does now Review by moors. git-svn-id: http://lampsvn.epfl.ch/svn-repos/scala/scala/trunk@25301 5e8d7ff9-d8ef-0310-90f0-a4852d11357a
1 parent 9050577 commit c36ce49

File tree

2 files changed

+55
-30
lines changed

2 files changed

+55
-30
lines changed

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

Lines changed: 50 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2390,13 +2390,19 @@ 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 {
24002406
// params are needed to keep track of variance (see mapOverArgs in SubstMap)
24012407
assert(typeArgs.isEmpty || sameLength(typeArgs, params))
24022408
// var tid = { tidCount += 1; tidCount } //DEBUG
@@ -2409,14 +2415,16 @@ A type's typeSymbol should never be inspected directly.
24092415
val level = skolemizationLevel
24102416

24112417
/** 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
2418+
* ''two instances'' of `TypeVar` that share the ''same'' `TypeConstraint`.
2419+
*
2420+
* `constr` for `?CC` only tracks type constructors anyway,
2421+
* so when `?CC[Int] <:< List[Int]` and `?CC[String] <:< Iterable[String]`
2422+
* `?CC's` hibounds contains List and Iterable.
24152423
*/
24162424
def applyArgs(newArgs: List[Type]): TypeVar =
24172425
if (newArgs.isEmpty) this // SubstMap relies on this (though this check is redundant when called from appliedType...)
24182426
else TypeVar(origin, constr, newArgs, params) // @M TODO: interaction with undoLog??
2419-
// newArgs.length may differ from args.length (could've been empty before)
2427+
// newArgs.length may differ from args.length (could've been empty before)
24202428
// 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]
24212429
// we need to track a TypeVar's arguments, and map over them (see TypeMap::mapOver)
24222430
// TypeVars get applied to different arguments over time (in asSeenFrom)
@@ -2427,15 +2435,16 @@ A type's typeSymbol should never be inspected directly.
24272435
// they share the same TypeConstraint instance
24282436

24292437
// <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)
2438+
// invariant: before mutating constr, save old state in undoLog
2439+
// (undoLog is used to reset constraints to avoid piling up unrelated ones)
24312440
def setInst(tp: Type) {
24322441
// assert(!(tp containsTp this), this)
24332442
undoLog record this
24342443
constr.inst = tp
24352444
}
24362445

24372446
def addLoBound(tp: Type, isNumericBound: Boolean = false) {
2438-
assert(tp != this) // implies there is a cycle somewhere (?)
2447+
assert(tp != this, tp) // implies there is a cycle somewhere (?)
24392448
//println("addLoBound: "+(safeToString, debugString(tp))) //DEBUG
24402449
undoLog record this
24412450
constr.addLoBound(tp, isNumericBound)
@@ -2504,27 +2513,38 @@ A type's typeSymbol should never be inspected directly.
25042513
* Checks subtyping of higher-order type vars, and uses variances as defined in the
25052514
* type parameter we're trying to infer (the result will be sanity-checked later).
25062515
*/
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)
2516+
def unifyFull(tpe: Type) = {
2517+
// Since the alias/widen variations are often no-ops, this
2518+
// keenly collects them in a Set to avoid redundant tests.
2519+
Set(tpe, tpe.widen, tpe.dealias, tpe.widen.dealias) exists { tp =>
2520+
sameLength(typeArgs, tp.typeArgs) && {
2521+
// this is a higher-kinded type var with same arity as tp.
2522+
// side effect: adds the type constructor itself as a bound
2523+
addBound(tp.typeConstructor)
2524+
if (isLowerBound) isSubArgs(tp.typeArgs, typeArgs, params)
2525+
else isSubArgs(typeArgs, tp.typeArgs, params)
2526+
}
2527+
}
25122528
}
2529+
2530+
// There's a <:< test taking place right now, where tp is a concrete type and this is
2531+
// a typevar attempting to satisfy that test. If we determine that the subtype test is
2532+
// infeasible, we'll return false. Otherwise will retain all the constraints implied by
2533+
// this test, which will eventually be lub/glbed to instantiate the typevar. We must
2534+
// be able to find a type with the same number of typeargs among the base types of tp,
2535+
// or tp after dealiasing/widening.
25132536

25142537
/** 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-
}
2538+
def unifyBaseTypes =
2539+
if (isLowerBound) tp.baseTypeSeq exists unifyFull
2540+
else tp.baseTypeSeq.toList forall unifyFull
2541+
2542+
if (suspended)
2543+
checkSubtype(tp, origin)
2544+
else if (constr.instValid) // type var is already set
2545+
checkSubtype(tp, constr.inst)
2546+
else // relax precision seeking a type whose shape matches the typevar
2547+
isRelatable(tp) && (unifySimple || unifyFull(tp) || unifyBaseTypes)
25282548
}
25292549

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

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)