-
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
Handle recursions in isFullyDefined #15443
Conversation
If one indulges in cycles to heavily it can happen that we run into an infinite recursion in `fullyDefinedType` even before we had a chance to run `checkNonCyclic`. In this case we now diagnose the stack overflow as an error message instead of fialing with an internal error. Fixes scala#15311
Note that the original example can be turned into an infinite loop by replacing trait Template[+T <: Template[T]]:
type Clone <: T { type Clone <: Template.this.Clone }
val self :Clone
type Food = Template[_]
class Ham extends Template[Ham]:
type Clone = Ham
val self = this
def eat[F <: Template[F]](food :F) :F = food.self.self
val ham = new Ham
val food :Food = ham
def test =
eat(ham)
eat(food.self) |
It's not an infinite loop, it just takes a minute or two to spit out the "Recursion limit exceeded" error. |
How I fixed it: That one took some time to diagnose the root cause. The error message was java.lang.Error: internal error: type of right-hand side Playground.Template[Playground.food.T] is not fully defined, pos = [929..933..952] I printed the type with -Yprint-debug, and then by displaying its val result =
try new IsFullyDefinedAccumulator(force)(using nestedCtx).process(tp)
catch case ex: StackOverflowError =>
false Indeed there was a stack overflow. That caused That presented another problem: |
@@ -28,11 +28,11 @@ object Inferencing { | |||
* but only if the overall result of `isFullyDefined` is `true`. | |||
* Variables that are successfully minimized do not count as uninstantiated. | |||
*/ | |||
def isFullyDefined(tp: Type, force: ForceDegree.Value)(using Context): Boolean = { | |||
def isFullyDefined(tp: Type, force: ForceDegree.Value, handleOverflow: Boolean = false)(using Context): Boolean = { |
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.
I just re-read the diff and the "how I fixed it" comment (because I'm looking at handleRecursive and trace) and shouldn't the default here be true and fullDefinedType
pass true so that it can do it's special handling? My assumption is that we would want all other calls of isFullyDefined
to continue to return false
instead of throwing.
Or are do we want to throw because isFullyDefined
is call transitively from IsFullyDefinedAccumulator
and we want those handled by the handleRecursive
you added? My point is handleOverflow
seems to never be non-default, was that for future possible uses?
If one indulges in cycles too heavily it can happen that we run into
an infinite recursion in
fullyDefinedType
even before we had a chanceto run
checkNonCyclic
. In this case we now diagnose the stack overflowas an error message instead of failing with an internal error.
Fixes #15311