-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Prune constraints that could turn into cycles #1058
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
c24ece5
55832b8
f20b11a
8358e97
73e13e8
7d3f6d0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ import Types._, Contexts._, Symbols._ | |
import Decorators._ | ||
import config.Config | ||
import config.Printers._ | ||
import collection.mutable | ||
|
||
/** Methods for adding constraints and solving them. | ||
* | ||
|
@@ -35,6 +36,18 @@ trait ConstraintHandling { | |
|
||
private def addOneBound(param: PolyParam, bound: Type, isUpper: Boolean): Boolean = | ||
!constraint.contains(param) || { | ||
def occursIn(bound: Type): Boolean = { | ||
val b = bound.dealias | ||
(b eq param) || { | ||
b match { | ||
case b: AndOrType => occursIn(b.tp1) || occursIn(b.tp2) | ||
case b: TypeVar => occursIn(b.origin) | ||
case _ => false | ||
} | ||
} | ||
} | ||
if (Config.checkConstraintsSeparated) | ||
assert(!occursIn(bound), s"$param occurs in $bound") | ||
val c1 = constraint.narrowBound(param, bound, isUpper) | ||
(c1 eq constraint) || { | ||
constraint = c1 | ||
|
@@ -222,11 +235,67 @@ trait ConstraintHandling { | |
//checkPropagated(s"adding $description")(true) // DEBUG in case following fails | ||
checkPropagated(s"added $description") { | ||
addConstraintInvocations += 1 | ||
|
||
def addParamBound(bound: PolyParam) = | ||
if (fromBelow) addLess(bound, param) else addLess(param, bound) | ||
|
||
/** Drop all constrained parameters that occur at the toplevel in `bound` and | ||
* handle them by `addLess` calls. | ||
* The preconditions make sure that such parameters occur only | ||
* in one of two ways: | ||
* | ||
* 1. | ||
* | ||
* P <: Ts1 | ... | Tsm (m > 0) | ||
* Tsi = T1 & ... Tn (n >= 0) | ||
* Some of the Ti are constrained parameters | ||
* | ||
* 2. | ||
* | ||
* Ts1 & ... & Tsm <: P (m > 0) | ||
* Tsi = T1 | ... | Tn (n >= 0) | ||
* Some of the Ti are constrained parameters | ||
* | ||
* In each case we cannot leave the parameter in place, | ||
* because that would risk making a parameter later a subtype or supertype | ||
* of a bound where the parameter occurs again at toplevel, which leads to cycles | ||
* in the subtyping test. So we intentionally narrow the constraint by | ||
* recording an isLess relationship instead (even though this is not implied | ||
* by the bound). | ||
* | ||
* Narrowing a constraint is better than widening it, because narrowing leads | ||
* to incompleteness (which we face anyway, see for instance eitherIsSubType) | ||
* but widening leads to unsoundness. | ||
* | ||
* A test case that demonstrates the problem is i864.scala. | ||
* Turn Config.checkConstraintsSeparated on to get an accurate diagnostic | ||
* of the cycle when it is created. | ||
* | ||
* @return The pruned type if all `addLess` calls succeed, `NoType` otherwise. | ||
*/ | ||
def prune(bound: Type): Type = bound match { | ||
case bound: AndOrType => | ||
val p1 = prune(bound.tp1) | ||
val p2 = prune(bound.tp2) | ||
if (p1.exists && p2.exists) bound.derivedAndOrType(p1, p2) | ||
else NoType | ||
case bound: TypeVar if constraint contains bound.origin => | ||
prune(bound.underlying) | ||
case bound: PolyParam if constraint contains bound => | ||
if (!addParamBound(bound)) NoType | ||
else if (fromBelow) defn.NothingType | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about narrowing a bit less by using something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That would not add anything. By recording the isLess we get the bounds anyway. |
||
else defn.AnyType | ||
case _ => | ||
bound | ||
} | ||
|
||
try bound match { | ||
case bound: PolyParam if constraint contains bound => | ||
if (fromBelow) addLess(bound, param) else addLess(param, bound) | ||
addParamBound(bound) | ||
case _ => | ||
if (fromBelow) addLowerBound(param, bound) else addUpperBound(param, bound) | ||
val pbound = prune(bound) | ||
pbound.exists && ( | ||
if (fromBelow) addLowerBound(param, pbound) else addUpperBound(param, pbound)) | ||
} | ||
finally addConstraintInvocations -= 1 | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
object C { | ||
val a: Int = 1 | ||
val b: Int = 2 | ||
val c: Int = 2 | ||
|
||
trait X[T] | ||
implicit def u[A, B]: X[A | B] = new X[A | B] {} | ||
def y[T](implicit x: X[T]): T = ??? | ||
val x: a.type & b.type | b.type & c.type = y | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You say "another constrained parameter" but the check does
b eq param
, so shouldn't you say something like "Make sure none of the bounds of a parameter in an OrderingConstraint contains this parameter at its toplevel" ?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, indeed. Can you make that change please? Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done, also fixed the documentation of
addConstraint
.