-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Can we make capture checking use less global inference? #22808
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
Comments
There seems to be wrong behavior even with the current scheme. Here is a variant of the example: class Box[T](x: T):
def m: T = ???
def test(io: Object^): Unit =
def foo(): Unit = bar()
def bar(): Unit =
val x = () =>
foo()
val y = Box(io)
println(y.m)
val _: () -> Unit = x // error We get an error on the last line, as expected:
But if we duplicate the last line and place a copy of it earlier, it compiles without errors class Box[T](x: T):
def m: T = ???
def test(io: Object^): Unit =
def foo(): Unit = bar()
def bar(): Unit =
val x = () =>
foo()
val _: () -> Unit = x // no error!
val y = Box(io)
println(y.m)
val _: () -> Unit = x // no error! |
In fact, computing use sets eagerly with cycle detection can collide with the lazy evaluation of types. E.g. def foo() = bar()
def bar(): Int = foo() If we analyze the body of An alternative scheme could be:
|
The issue with the scheme in the previous comments is that use sets also appear in other contexts. For instance, the use set of a class is also included in capture set of the type of In a sense the best scheme is to be more lazy. Maybe still keep solving capture sets of vals or defs (but we could also drop this part). But otherwise we solve a capture set variable at the moment we apply a map to it. So we never have If a subcapturing comparison for some other set fails for the same reason, we probably should fail with an internal error to figure out why this happens. |
This is addressed in #22910. We now drop regular maps (these evaluate capture set elements into constants) and fuse chains of BiTypeMaps into a single map. |
The capture checker employs a global type inference scheme using a propagation constraint solver. Capture sets are constrained by subcapturing constraints or are defined as results of type maps of other capture sets. This can lead to long chains of maps that are hard to debug and that make formulation of capture checking algorithms more difficult.
Can we use a more local scheme where we "solve" constraints of capture sets for each inferred result type? That would largely avoid the need for stored type maps. But it would work only if we can compute the type of each definition only from its local scope. The following example shows that this is not always possible:
If we print this with
-Xprint:cc
, we get:The interesting bit is the type of
x
. How did the checker find out it capturesio
? Here's how:x
's right hand side callsfoo
, so the capture set of the closure includes the use set offoo
.foo
callsbar
so the use set offoo
includes the use set ofbar
.io
is used as an argument toBox
inbar
but this is in boxed form, so this does not contribute to the use set ofbar
.y
ends up to beBox[box Object^{io}]
.bar
unboxesy.x
of typey*
in the finalprintln
.y
's type to the use set ofbar
.{io}
, so that set ends up as the use set ofbar
andfoo
and as the capture set of the type ofx
.This means that to compute the type of
x
we have to know the use sets offoo
andbar
, which means we have to also analyze the rest ofbar
after the definition ofx
. What's more, this computation involves the fullcapture checking machinery including reach captures and their underlying deep capture sets. So there does not seem to be an easy way to have a simpler use set computation followed by local capture set inference.
On the other hand, maybe we could use a standard fixed point iteration approach:
In the example above, this would lead to the following steps:
foo
, we need the use set ofbar
, so we capture-checkbar
.bar
, we need to check the RHS ofx
, which means we need the use set offoo
.foo
is currently checked, we assume its use set is as computed so far, i..e. it is empty, and we mark it as observed.() -> Unit
forx
.bar
which gives a use set of{io}
.foo
which gives again a use set of{io}
. We remark that this use set has changed after it was observed.test
with use sets of{io}
forbar
andfoo
.I believe cycles will be quite rare, so computationally this might be a win.
The text was updated successfully, but these errors were encountered: