-
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
Consolidate CC #21863
base: main
Are you sure you want to change the base?
Consolidate CC #21863
Conversation
Previously, we violated that assumption is we too the deep capture set of a capture reference wiht singleton type.
Count in dcs exactly those locations where a cap gets replaced by a reach capability.
Simplify code that handles applications, avoiding adding pieces of mutable state.
The correct point to address charging reach capabilities is in markFree itself: When a reach capability goes out of scope, and that capability is not a parameter, we need to continue with the underlying capture set.
Drop the scheme where we only charge the last arrow of a curried lambda with elements used in the body. On the one hand, this is unsound without compensation measures (like, restricting to reach capabilities, or taking all capture sets of a named curried function as the underlying reference). On the other hand, this should be generalized to all closures and anonymous functions forming the right hand sides of methods.
9865f93
to
70af983
Compare
Use sets of nested methods are anyway charged on call.
This gives better error messages. Previously we thought this would make reach capabilities unsound, but I don't see an issue with the latest design.
With our current @use scheme, this is unsound. We leave the possibility to re-enable as a Config option which is disabled by default and comes with a warning that enabling it would be unsound.
Check that type parameters of methods and parent traits don't get instantiated with types containing a `cap` anywhere in covariant or invariant position.
At this point, I feel that curried capture for reach capabilities makes sense no more, given that
Besides, functions like Given the above, shall we drop the special curried capture mechanism for reach capability completely? Reach capabilities, like other capabilities, bubbles all the way up to the top, unless stopped by a boxed environment. Any function that uses the reach capability of its parameter is then automatically be required to be marked as |
In fact that's what is currently implemented. There's a def test3(xs: List[() => Unit]): () ->{xs*} Unit = () =>
println(xs.head) // error, ok under deferredReaches Then [C^] -> (xs: List[() ->{C^} Unit]) -> () ->{C^} Unit` That's a pure function, so we have some justification to treat |
I see, I agree. Environment avoidance can make deferred reaches sound, since it is basically a mechanism for charging effects on subsequent arrows of a function spine correctly. (note that since effects can not only be those of function itself, but can also be some effect variable instantiated by the application of the function, like capture variables or reach capabilities) |
It turns out that the subcapturing rule between a reach capability and its dcs is not sound. The problem is that type substitution breaks it. In other words, this rule is unsound:
Type preservation does not preserve it. As an example, given the following context:
Note that
Therefore, under this context, one may derive:
And this will not be preserved by a type substitution, for instance, [X := Op^{io}]. After applying the type substitution, the context becomes:
and we have
Therefore, under this context, we can only derive:
And the relation `{x*} <: {async}` is falsified after the type substitution! In other words, type application could break typing. Based on this idea, we could construct the following example:
The |
Also: add test that reach capabilities are contained inside boxes
Yes, that's bad. The alternative would be to always take import language.experimental.captureChecking
import caps.use
class Box[T](items: Seq[T^]):
def getOne: T^{items*} = ???
object Box:
def getOne[T](@use items: Seq[T^]): T^{items*} =
val bx = Box(items)
bx.getOne we'd get with that change:
Other tests failing in similar ways are:
What can we do to accept these, but rule out the unsound one? |
In the previous example, val bx: Box[T]{val items: Seq[T^{items*}]} |
We currently have have for any reference
Since colltest5 fails, the stdlib is likely to fail as well. The problem is demonstrated in this extract of colltest5: trait Iterator[+A]:
self: Iterator[A]^ =>
trait View[+A]:
def iterator: Iterator[A]^{this}
object View:
def fromIterator[A](it: => Iterator[A]^): View[A]^{it} = new View[A]:
def iterator: Iterator[A]^{this} = it When we change the rules it fails with:
The root cause is that given So I think until we work out span captures we are stuck on this one. |
About adding environment avoidance. I tried that as well, but a great number of tests (>40) break. One problem is that we often treat def f() =
val r: Ref^ = newRef(1)
r.get But that causes a failure when we use |
I could give it a try after the submission. It's been some while since my last compiler hacking. |
That's right. This leads me to the realisation that right now with the addition of environment avoidance existential types are useless in Capless:
This seems really bad. |
Here it is! import language.experimental.captureChecking
import caps.{cap, use}
trait IO
trait Async
def main(io: IO^, async: Async^) =
def bad[X](ops: List[(X, () ->{io} Unit)])(f: () ->{ops*} Unit): () ->{io} Unit = f
def runOps(@use ops: List[(() => Unit, () => Unit)]): () ->{ops*} Unit =
() => ops.foreach((f1, f2) => { f1(); f2() })
def delayOps(@use ops: List[(() ->{async} Unit, () ->{io} Unit)]): () ->{io} Unit =
val runner: () ->{ops*} Unit = runOps(ops)
val badRunner: () ->{io} Unit = bad[() ->{async} Unit](ops)(runner)
// it uses both async and io, but we losed track of async.
badRunner (ps: Sorry for accidentally closing this PR, I misclicked) |
We can use the dcs only if there are no type variables.
The additional purity in the asInstanceOf target is not needed
Retain only caps.unsafe.unsafeAssumePure
Don't show an `(ex$n: Exists) ->` if the bound variable does not appear in the result. The full type will be shown under -Ycc-debug. Also, avoid spurious ineffective mappings in widenReach.
65a1e80
to
d34ce30
Compare
A by-name Closure node, which is produced by phase ElimByName gets a target type to indicate it's a contextual zero parameter closure. But for the purposes of rechecking and capture checking, it needs to be treated like a function. In particular the type of the closure needs to be derived from the result type of the anonymous function. Fixes scala#21920
d34ce30
to
e3c91f3
Compare
A refactored and consolidated capture checker without any drastic changes to the algorithm. The main changes are:
cap
instead of checking that we do not box or unboxcap
.@unbox
to@use
Based on #21861