When solving type variables propagate solutions to bounds of variables left undetermined#6140
Conversation
e7179bd to
756c810
Compare
adriaanm
left a comment
There was a problem hiding this comment.
The required change is likely small, but I'd like you to explore the potential regression outlined in my comment.
| val bounds = undet.info.bounds | ||
| val substBounds = bounds.subst(okparams, okargs) | ||
| if(bounds ne substBounds) | ||
| undet.setInfo(substBounds) |
There was a problem hiding this comment.
I think we should use updateInfo instead of setInfo. The latter is problematic here because it wipes out undet's type history. Since we didn't create the undet symbol, we can't be sure that's ok.. In practice, this isn't a problem if we're early on in the compiler and there is no type history to wipe out anyway (which is already a tenuous assumption in typedAppliedTypeTree). However, type inference definitely can be triggered in later phases.
I seem to remember fixing bugs resulting of similar abuse of setInfo in the GADT arena. It's also possible, as always, that I'm mistaken, but I'd like you to investigate this (e.g., by trying to construct a run where the type history is non-empty and dumping it before calling setInfo), and hear what @lrytz and @retronym think before merging.
There was a problem hiding this comment.
I'm pretty much convinced that updateInfo is the right thing to do rather than setInfo, but I've not been able to construct an example with a non-trivial TypeHistory. Prior to the setInfo call above we have a single entry either for namer or typer, and afterwards the entry is overwritten yielding exactly the same result as if updateInfo was called. Clearly this could go wrong if the TypeHistory was more interesting, but I can't see a way to get that to happen. I think that might be because these are all undetermined params, and will have been freshly cloned (eg. in Inferencer.isApplicable).
I'm happy to follow up on this if anyone can suggest a way of accumulating a more interesting TypeHistory, but for now I'm just going to switch to updateInfo because that appears to be the right thing to do in any case.
There was a problem hiding this comment.
The TypeHistory would become more interesting (and relevant) if this method is called later during compilation. Let's say specialization. It's certainly far from trivial to come up with an example (or I would have included it in my comment). If nothing comes up after playing around some more in a debugger and observing what happens, I'm happy to conclude this thread with the side effect of leaving this breadcrumb in the mind of future contributors :-)
|
This appears to be fine with |
756c810 to
5cc229c
Compare
|
Also rebased. |
|
Thanks for the quick updates to the code. Please also take into account the style guide for commit messages, as explained in the other PR. Squash the commits, and add a comment to the code that makes it clear |
When instantiating type parameters and retracting unsolved ones via adjustTypeArgs and friends we end up with a list of solved type params/variables and a list of still-undetermined type parameters. Typically this is followed by a substitution of the solved variables into the positions of the corresponding parameters, and type checking proceeds with the substitutions eliminated from the list of undetermined type parameters. However, the substituted parameters might occur as components of the bounds of the type parameters which are yet to be determined and, prior to this commit, these occurrences are not substituted into with the solved variables. This leads to issues of the form seen in scala/bug#10528. This commit performs the substitution similarly to the way it is done in enhanceBounds in typedAppliedTypeTree and fixes scala/bug#10528. The new addition is largely a duplicate of the existing mechanism in part to enable it to use updateInfo rather than setInfo and so preserve type histories. There is a TODO to investigate whether the original mechanism should also attempt to preserve type histories and whether the two methods can be combined.
5cc229c to
dd31672
Compare
|
Added comments, squashed and commit message massaged. |
|
This one seems to have caused a regression (maybe, by unearthing an latent bug) trait BaseStream[T, S <: BaseStream[T, S]]
trait Stream[T] extends BaseStream[T, Stream[T]]
trait IntStream extends BaseStream[Integer, IntStream]
sealed trait SS[T, S <: BaseStream[_, S]]
object SSImplicits extends Low {
implicit val IntValue: SS[Int, IntStream] = null
}
trait Low {
implicit def anyStreamShape[T]: SS[T, Stream[T]] = null
}
import SSImplicits.{IntValue, anyStreamShape}
class Test {
implicit def f[A, S <: BaseStream[_, S], CC](a: A)(implicit ss: SS[A, S]): S = ???
// switch these lines and typechecking the body of `def x` fails in 2.13.x
x
y
def x = f(0): IntStream
def y = f[String, Stream[String], Vector[String]]("")
}
|
| val bounds = undet.info.bounds | ||
| val substBounds = bounds.subst(okparams, okargs) | ||
| if(bounds ne substBounds) | ||
| undet.updateInfo(substBounds) |
There was a problem hiding this comment.
The problem is likely that these sharpened bounds are only intended to be seen for a single candidate in typedImplicit0, but end up leaking into later candidates.
There was a problem hiding this comment.
Yep, seems to be the case:
| | | | [search #7] start `[A, S <: BaseStream[_, S], CC](a: A)(implicit ss: SS[A,S])S`, searching for adaptation to pt=Stream[Int] => IntStream (silent: method x in Test) implicits disabled
| | | | [search #7] considering f
| | | | |-- f BYVALmode-EXPRmode-FUNmode-POLYmode (silent: method x in Test) implicits disabled
| | | | | [adapt] [A, S <: BaseStream[_, S], CC](a: A)(implicit ss: SS[A,S])S adapted to [A, S <: BaseStream[_, S], CC](a: A)(implicit ss: SS[A,S])S
| | | | | \-> (a: A)(implicit ss: SS[A,S])S
| | | | solving for (A: ?A, S: ?S, CC: ?CC)
enhanceBounds(type S#21256) updated <: BaseStream#6553[_, S#21256] to <: BaseStream#6553[_, S#21256]
| | | | [search #8] start `[A, S <: BaseStream[_, S], CC](a: A)(implicit ss: SS[A,S])S` inferring type S, searching for adaptation to pt=SS[Stream[Int],S] (silent: method x in Test) implicits disabled
| | | | [search #8] considering anyStreamShape
| | | | solving for (T: ?T)
| | | | [adapt] anyStreamShape adapted to [T]=> SS[T,Stream[T]] based on pt SS[Stream[Int],S]
| | | | [search #8] solve tvars=?S, tvars.constr= >: Stream[Stream[Int]] <: Stream[Stream[Int]]
| | | | solving for (S: ?S)
enhanceBounds(type S#21256) updated <: BaseStream#6553[_, S#21256] to <: BaseStream#6553[_, Stream#6580[Stream#6580[Int#1192]]]
| | | | [search #8] success inferred value of type SS[Stream[Int],=?Stream[Stream[Int]]] is SearchResult(SSImplicits.anyStreamShape[Stream[Int]], TreeTypeSubstituter(List(type S),List(Stream[Stream[Int]])))
| | | | [adapt] f adapted to [A, S <: BaseStream[_, S], CC](a: A)(implicit ss: SS[A,S])S based on pt Stream[Int] => IntStream
| | | | [search #9] start `[A, S <: BaseStream[_, S], CC](a: A)(implicit ss: SS[A,S])S`, searching for adaptation to pt=(=> Stream[Int]) => IntStream (silent: method x in Test) implicits disabled
sandbox/test.scala:23: error: type mismatch;
found : Stream[Int]
required: IntStream
def x = f(0): IntStream
^
⚡ git diff
diff --git a/src/compiler/scala/tools/nsc/typechecker/Infer.scala b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
index 481307467c..4c1994ef61 100644
--- a/src/compiler/scala/tools/nsc/typechecker/Infer.scala
+++ b/src/compiler/scala/tools/nsc/typechecker/Infer.scala
@@ -723,6 +723,9 @@ trait Infer extends Checkable {
val substBounds = bounds.subst(okparams, okargs)
if(bounds ne substBounds)
undet.updateInfo(substBounds)
+ settings.uniqid.value = true
+ println(s"enhanceBounds($undet) updated $bounds to $substBounds")
+ settings.uniqid.value = false
}There was a problem hiding this comment.
I'll take a look.
There was a problem hiding this comment.
Hmm, sounds like we need to clone the undetermined params for each run of the search? Hopefully not too expensive
There was a problem hiding this comment.
Yup, that was my thought too. I'm surprised I wasn't doing that already.
There was a problem hiding this comment.
Do you think undoLog could be used for this?
The bounds propagation introduced in scala#6140 caused a regression in scala/scala-java8-compat#97 because bounds sharpened while ranking implicit candidates leaked from candidates tested earlier to those tested later. This commit fixes that by saving and restoring the infos of the symbols of the undetermined type parameters around the call to type check the implicit candidate which tests for it's applicability. Initially it seemed like this ought to be a job for undoLog or Context#savingUndeteriminedTypeParams, but neither of those do the right thing here. UndoLog doesn't help because it affects the constraint on the corresponding TypeVar rather than the info of the symbol; and savingUndeterminedTypeParams doesn't help because the relevant call to typedImplicit shares the enclosing ImplicitSearch#undetParams rather than looking at the context.
|
Bug identified by @retronym reported here: scala/bug#10708. PR fixing it here: #6308. |
In inferMethodInstance a new list of undetermined symbols is computed and returned. However, the bounds enhancement introduced in scala#6140 is applied to the list of previously undetermined symbols, rather than the new list. This commit applies the bounds enhancement to the newly computed list. None of the other callsites of enhanceBounds appear to have the same issue. Fixes scala/bug#10758.
In inferMethodInstance a new list of undetermined symbols is computed and returned. However, the bounds enhancement introduced in scala#6140 is applied to the list of previously undetermined symbols, rather than the new list. This commit applies the bounds enhancement to the newly computed list. None of the other callsites of enhanceBounds appear to have the same issue. Fixes scala/bug#10758.
When instantiating type parameters and retracting unsolved ones via
adjustTypeArgsand friends we end up with a list of solved type params/variables and a list of still-undetermined type parameters. Typically this is followed by a substitution of the solved variables into the positions of the corresponding parameters, and type checking proceeds with the substitutions eliminated from the list of undetermined type parameters.However, the substituted parameters might occur as components of the bounds of the type parameters which are yet to be determined and, prior to this PR, these occurrences are not substituted into with the solved variables. This leads to issues of the form seen in scala/bug#10528. This PR performs the substitution similarly to the way it is done in
enhanceBoundsintypedAppliedTypeTreeand fixes scala/bug#10528.I didn't attempt to move this functionality into
AdjustedTypeArgswhere it probably belongs, because the way the latter is structured would mean that we'd be performing side effects in a pattern match, albeit an irrefutable one. I think some restructuring of this logic would be worthwhile. I think that it should also be possible now to reinstate a commented out call tocheckBoundshere with something similar, but I think that would also benefit from some reworking ofAdjustedTypeArgs.All (par)tests pass.