Skip to content

Can we make capture checking use less global inference? #22808

Closed
@odersky

Description

@odersky

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:

case class Box[T](x: T)
def test(io: Object^): Unit =
  def foo(): Unit = bar()
  def bar(): Unit =
    val x = () =>
      foo()
    val y = Box(io)
    println(y.x)

If we print this with -Xprint:cc, we get:

def test(io: Object^): Unit =
      {
        def foo(): Unit = bar()
        def bar(): Unit =
          {
            val x: () ->{io} Unit = () => foo()
            val y: Box[box Object^{io}]^? = Box.apply[box Object^{io}](io)
            println(y.x)
          }
        ()
      }

The interesting bit is the type of x. How did the checker find out it captures io? Here's how:

  • x's right hand side calls foo, so the capture set of the closure includes the use set of foo.
  • foo calls bar so the use set of foo includes the use set of bar.
  • io is used as an argument to Box in bar but this is in boxed form, so this does not contribute to the use set of bar.
  • The inferred type of y ends up to be Box[box Object^{io}].
  • bar unboxes y.x of type y* in the final println.
  • This means we charge the deep capture set of y's type to the use set of bar.
  • The deep capture set is {io}, so that set ends up as the use set of bar and foo and as the capture set of the type of x.

This means that to compute the type of x we have to know the use sets of foo and bar, which means we have to also analyze the rest of bar after the definition of x. What's more, this computation involves the full
capture 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:

  • Keep track of which definitions are currently traversed.
  • When including call captures, compute the use set of the callee before proceeding, providing the callee is not currently analyzed.
  • If the callee is currently analyzed we have a cycle like the one in the example. Pick the current use set of the callee and mark that set as "observed".
  • If an observed use set acquires new elements, set a flag that capture set inference needs to be repeated.
  • If at the end of the inference the flag is set, we do a new iteration, with the use sets computed so far.

In the example above, this would lead to the following steps:

  • When capture-checking foo, we need the use set of bar, so we capture-check bar.
  • When capture checking bar, we need to check the RHS of x, which means we need the use set of foo.
  • Since 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.
  • This gives the wrong type () -> Unit for x.
  • We complete checking of bar which gives a use set of {io}.
  • We complete checking of foo which gives again a use set of {io}. We remark that this use set has changed after it was observed.
  • We restart the whole capture checking of test with use sets of {io} for bar and foo.

I believe cycles will be quite rare, so computationally this might be a win.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions