-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Avoid incorrect simplifications when updating bounds in the constraint #16410
Conversation
That's brilliant. 😄 Amazing sleuthing. |
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.
LGTM. Took me too long to see the difference between validBoundFor
and validBoundsFor
, probably a "skill issue" on my side.
@@ -88,6 +88,8 @@ abstract class Constraint extends Showable { | |||
* - Another type, indicating a solution for the parameter | |||
* | |||
* @pre `this contains param`. | |||
* @pre `tp` does not contain top-level references to `param` | |||
* (see `ensureNonCyclic`) |
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.
ensureNonCyclic is no longer there.
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.
Should probably be validBounds
. Also: Should there be a way to test the precondition if a Config flag is set? Or can you argue statically that the precondition is always true? If yes, maybe add a note to this effect.
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.
Should there be a way to test the precondition if a Config flag is set?
I believe this is already checked a posteriori by the call to checkWellFormed()
when Config.checkConstraintsNonCyclic is set.
Otherwise LGTM |
3dafe20
to
d79bf05
Compare
When combining an old and a new bound, we use `Type#&`/`Type#|` which perform simplifications. This is usually fine, but if the new bounds refer to the parameter currently being updated, we can run into cyclic reasoning issues which make the simplifications invalid after the update. We already have logic for handling self-references in parameter bounds: `updateEntry` calls `ensureNonCyclic` which sanitizes the type, but at this point the simplifications have already occured. This commit simply moves the logic out of `updateEntry` so that we can sanitize the new bounds before simplification. More precisely, we rename `ensureNonCyclic` to `validBoundsFor` which calls `validBoundFor`. Both are used to sanitize bounds where needed in `addOneBound` and `unify`. Since all calls to `updateEntry` now have sanitized bounds, we no longer need to sanitize them in `updateEntry` itself, we document this change by adding a pre-condition to `updateEntry`. For the record, here's how `ConstraintsTest#validBoundsInit` used to fail. It defines a method: def foo[S >: T <: T | Int, T <: String]: Any Before this commit, when `foo` was added to the current constraints, the constraint `S <: T | Int` was propagated to the lower bound `T` of `S`. The updated upper bound of `T` was thus set to: String & (T | Int) But because `Type#&` performs simplifications, this became T | (String & Int) by relying on the fact that at this point, `T <: String`. But in fact this simplified bound no longer ensures that `T <: String`! The self-reference was then replaced by `Any` in `OrderingConstraint#ensureNonCyclic`. After this commit, the problematic simplification no longer occurs since the new `T | Int` is sanitized to `Any` before being intersected with the old bound.
d79bf05
to
50eb0e9
Compare
When combining an old and a new bound, we use
Type#&
/Type#|
which perform simplifications. This is usually fine, but if the new bounds refer to the parameter currently being updated, we can run into cyclic reasoning issues which make the simplifications invalid after the update.We already have logic for handling self-references in parameter bounds:
updateEntry
callsensureNonCyclic
which sanitizes the type, but at this point the simplifications have already occured. This commit simply moves the logic out ofupdateEntry
so that we can sanitize the new bounds before simplification.More precisely, we rename
ensureNonCyclic
tovalidBoundsFor
which callsvalidBoundFor
(singular). Both are used to sanitize bounds where needed inaddOneBound
andunify
.Since all calls to
updateEntry
now have sanitized bounds, we no longer need to sanitize them inupdateEntry
itself, we document this change by adding a pre-condition toupdateEntry
.For the record, here's how
ConstraintsTest#validBoundsInit
used to fail. It defines a method:Before this commit, when
foo
was added to the current constraints, the constraintS <: T | Int
was propagated to the lower boundT
ofS
. The updated upper bound ofT
was thus set to:But because
Type#&
performs simplifications, this becameby relying on the fact that at this point,
T <: String
. But in fact this simplified bound no longer ensures thatT <: String
! The self-reference was then replaced byAny
inOrderingConstraint#ensureNonCyclic
. After this commit, the problematic simplification no longer occurs since the newT | Int
is sanitized toAny
before being intersected with the old bound.