Skip to content

Commit 253307a

Browse files
committed
Fix constraint merges with conflicting type variables
When merging two constraints we can end up in a situation where a type lambda is associated with different type variables in the two constraint. This happened when compiling concat.scala after introducing an additional tryEither for typechecking the function part of an application. That caused a constraint merge of two constraints over logically separate instances of `++`, which shared the same type lambda for `++` but associated it with different type variables. This situation can arise since for efficiency we do not always clone a type lambda before adding it to a constraint. The correct way to deal with the situation is to clone the type lambda with the conflicting TypeVars in one of the constraints before proceeding with the merge.
1 parent 72e5359 commit 253307a

File tree

5 files changed

+88
-23
lines changed

5 files changed

+88
-23
lines changed

compiler/src/dotty/tools/dotc/core/Constraint.scala

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ abstract class Constraint extends Showable {
1717

1818
type This <: Constraint
1919

20-
/** Does the constraint's domain contain the type parameters of `pt`? */
21-
def contains(pt: TypeLambda): Boolean
20+
/** Does the constraint's domain contain the type parameters of `tl`? */
21+
def contains(tl: TypeLambda): Boolean
2222

2323
/** Does the constraint's domain contain the type parameter `param`? */
2424
def contains(param: TypeParamRef): Boolean
@@ -106,14 +106,22 @@ abstract class Constraint extends Showable {
106106
*/
107107
def replace(param: TypeParamRef, tp: Type)(implicit ctx: Context): This
108108

109-
/** Is entry associated with `pt` removable? This is the case if
109+
/** Is entry associated with `tl` removable? This is the case if
110110
* all type parameters of the entry are associated with type variables
111111
* which have their `inst` fields set.
112112
*/
113-
def isRemovable(pt: TypeLambda): Boolean
113+
def isRemovable(tl: TypeLambda): Boolean
114114

115-
/** A new constraint with all entries coming from `pt` removed. */
116-
def remove(pt: TypeLambda)(implicit ctx: Context): This
115+
/** A new constraint with all entries coming from `tl` removed. */
116+
def remove(tl: TypeLambda)(implicit ctx: Context): This
117+
118+
/** A new constraint with entry `tl` renamed to a fresh type lambda */
119+
def rename(tl: TypeLambda)(implicit ctx: Context): This
120+
121+
/** The given `tl` in case it is not contained in this constraint,
122+
* a fresh copy of `tl` otherwise.
123+
*/
124+
def ensureFresh(tl: TypeLambda)(implicit ctx: Context): TypeLambda
117125

118126
/** The type lambdas constrained by this constraint */
119127
def domainLambdas: List[TypeLambda]

compiler/src/dotty/tools/dotc/core/OrderingConstraint.scala

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import collection.mutable
88
import printing.Printer
99
import printing.Texts._
1010
import config.Config
11+
import config.Printers.constr
1112
import reflect.ClassTag
1213
import annotation.tailrec
1314
import annotation.internal.sharable
@@ -503,6 +504,7 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
503504
}
504505

505506
def & (other: Constraint, otherHasErrors: Boolean)(implicit ctx: Context): OrderingConstraint = {
507+
506508
def merge[T](m1: ArrayValuedMap[T], m2: ArrayValuedMap[T], join: (T, T) => T): ArrayValuedMap[T] = {
507509
var merged = m1
508510
def mergeArrays(xs1: Array[T], xs2: Array[T]) = {
@@ -527,21 +529,71 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
527529
case (e1: TypeBounds, e2: TypeBounds) => e1 & e2
528530
case (e1: TypeBounds, _) if e1 contains e2 => e2
529531
case (_, e2: TypeBounds) if e2 contains e1 => e1
530-
case (tv1: TypeVar, tv2: TypeVar) if tv1.instanceOpt eq tv2.instanceOpt => e1
532+
case (tv1: TypeVar, tv2: TypeVar) if tv1 eq tv2 => e1
531533
case _ =>
532534
if (otherHasErrors)
533535
e1
534536
else
535537
throw new AssertionError(i"cannot merge $this with $other, mergeEntries($e1, $e2) failed")
536538
}
537539

538-
val that = other.asInstanceOf[OrderingConstraint]
540+
/** Ensure that constraint `c` does not associate different TypeVars for the
541+
* same type lambda than this constraint. Do this by renaming type lambdas
542+
* in `c` where necessary.
543+
*/
544+
def ensureNotConflicting(c: OrderingConstraint): OrderingConstraint = {
545+
def hasConflictingTypeVarsFor(tl: TypeLambda) =
546+
this.typeVarOfParam(tl.paramRefs(0)) ne c.typeVarOfParam(tl.paramRefs(0))
547+
// Note: Since TypeVars are allocated in bulk for each type lambda, we only
548+
// have to check the first one to find out if some of them are dufferent.
549+
val conflicting = c.domainLambdas.find(tl =>
550+
this.contains(tl) && hasConflictingTypeVarsFor(tl))
551+
conflicting match {
552+
case Some(tl) => ensureNotConflicting(c.rename(tl))
553+
case None => c
554+
}
555+
}
556+
557+
val that = ensureNotConflicting(other.asInstanceOf[OrderingConstraint])
558+
539559
new OrderingConstraint(
540560
merge(this.boundsMap, that.boundsMap, mergeEntries),
541561
merge(this.lowerMap, that.lowerMap, mergeParams),
542562
merge(this.upperMap, that.upperMap, mergeParams))
563+
}.reporting(res => i"constraint merge $this with $other = $res", constr)
564+
565+
def rename(tl: TypeLambda)(implicit ctx: Context): OrderingConstraint = {
566+
assert(contains(tl))
567+
val tl1 = ensureFresh(tl)
568+
def swapKey[T](m: ArrayValuedMap[T]) = m.remove(tl).updated(tl1, m(tl))
569+
var current = newConstraint(swapKey(boundsMap), swapKey(lowerMap), swapKey(upperMap))
570+
def subst[T <: Type](x: T): T = x.subst(tl, tl1).asInstanceOf[T]
571+
current.foreachParam {(p, i) =>
572+
current = boundsLens.map(this, current, p, i, subst)
573+
current = lowerLens.map(this, current, p, i, _.map(subst))
574+
current = upperLens.map(this, current, p, i, _.map(subst))
575+
}
576+
current.foreachTypeVar { tvar =>
577+
val TypeParamRef(binder, n) = tvar.origin
578+
if (binder eq tl) tvar.setOrigin(tl1.paramRefs(n))
579+
}
580+
constr.println(i"renamd $this to $current")
581+
current
543582
}
544583

584+
def ensureFresh(tl: TypeLambda)(implicit ctx: Context): TypeLambda =
585+
if (contains(tl)) {
586+
var paramInfos = tl.paramInfos
587+
if (tl.isInstanceOf[HKLambda]) {
588+
// HKLambdas are hash-consed, need to create an artificial difference by adding
589+
// a LazyRef to a bound.
590+
val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos
591+
paramInfos = TypeBounds(lo, LazyRef(_ => hi)) :: pinfos1
592+
}
593+
ensureFresh(tl.newLikeThis(tl.paramNames, paramInfos, tl.resultType))
594+
}
595+
else tl
596+
545597
override def checkClosed()(implicit ctx: Context): Unit = {
546598
def isFreeTypeParamRef(tp: Type) = tp match {
547599
case TypeParamRef(binder: TypeLambda, _) => !contains(binder)

compiler/src/dotty/tools/dotc/core/Types.scala

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3667,7 +3667,14 @@ object Types {
36673667
* `owningTree` and `owner` are used to determine whether a type-variable can be instantiated
36683668
* at some given point. See `Inferencing#interpolateUndetVars`.
36693669
*/
3670-
final class TypeVar(val origin: TypeParamRef, creatorState: TyperState) extends CachedProxyType with ValueType {
3670+
final class TypeVar(private var _origin: TypeParamRef, creatorState: TyperState) extends CachedProxyType with ValueType {
3671+
3672+
def origin: TypeParamRef = _origin
3673+
3674+
/** Set origin to new parameter. Called if we merge two conflicting constraints.
3675+
* See OrderingConstraint#merge, OrderingConstraint#rename
3676+
*/
3677+
def setOrigin(p: TypeParamRef) = _origin = p
36713678

36723679
/** The permanent instance type of the variable, or NoType is none is given yet */
36733680
private[this] var myInst: Type = NoType

compiler/src/dotty/tools/dotc/typer/ProtoTypes.scala

Lines changed: 1 addition & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -498,20 +498,7 @@ object ProtoTypes {
498498
tt.withType(tvar)
499499
}
500500

501-
/** Ensure that `tl` is not already in constraint, make a copy of necessary */
502-
def ensureFresh(tl: TypeLambda): TypeLambda =
503-
if (state.constraint contains tl) {
504-
var paramInfos = tl.paramInfos
505-
if (tl.isInstanceOf[HKLambda]) {
506-
// HKLambdas are hash-consed, need to create an artificial difference by adding
507-
// a LazyRef to a bound.
508-
val TypeBounds(lo, hi) :: pinfos1 = tl.paramInfos
509-
paramInfos = TypeBounds(lo, LazyRef(_ => hi)) :: pinfos1
510-
}
511-
ensureFresh(tl.newLikeThis(tl.paramNames, paramInfos, tl.resultType))
512-
}
513-
else tl
514-
val added = ensureFresh(tl)
501+
val added = state.constraint.ensureFresh(tl)
515502
val tvars = if (addTypeVars) newTypeVars(added) else Nil
516503
ctx.typeComparer.addToConstraint(added, tvars.tpes.asInstanceOf[List[TypeVar]])
517504
(added, tvars)

tests/pos/concats.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
object Test {
2+
type Name = String
3+
val CommonOpNames: Set[Name] = Set("OR", "XOR")
4+
val ConversionNames: Set[Name] = Set("toByte")
5+
val BooleanOpNames: Set[Name] = Set("ZOR") ++ CommonOpNames
6+
val NumberOpNames: Set[Name] = (
7+
Set("ADD")
8+
++ Set("UNARY_+", "UNARY_-")
9+
++ CommonOpNames
10+
)
11+
}

0 commit comments

Comments
 (0)