Skip to content

Adapt types according to read-only status #23332

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

Merged
merged 14 commits into from
Jun 11, 2025
Merged

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Jun 9, 2025

Extend type adaptation to read-only status.

Two big improvements:

  • we also adapt if the target type is a mutable type with a read-only capture set. So the following compiles:

    class Ref extends Mutable { ... }
    val x: Ref^{cap.rd} = Ref()
  • we also adapt deeply inside a type. So the following compiles:

     val refs = List(Ref(), Ref())
     val rdrs: List[Ref^{cap.rd}] = refs

    We even try to track arguments through base types, so the following also compiles:

    val rdrs2: Seq[Ref^{cap.rd}] = refs

    But there are some limits, correlating actual and expected type through base types and as-seen-from is hard.

In the end I had to abandon the original idea to roll everyhting into subtyping. I got the basics to work, but the problem then was that we use deep capture sets in separation checking, and those were not adapted. To do this, we'd have to somehow figure out what a dcs relative to an expected type is. This looks deeply non-trivial, and would probably in the end come down to techniques similar to the adaptation in this PR. So the benefit of also adding and inferring qualifiers to capture sets are not clear.

EDIT: We do need qualifiers for soundness as @Linyxus example below shows. It's now in ro-mut-conformance.scala.
So the adaptation commit has been rebased on the mutability qualifiers branch.

@odersky odersky added the area:capabilities Issues tied with scala.caps.Capabilities label Jun 9, 2025
@odersky odersky requested review from noti0na1 and Linyxus June 9, 2025 14:35
@odersky odersky assigned noti0na1 and unassigned noti0na1 Jun 9, 2025
Copy link
Contributor

@Linyxus Linyxus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall!

So the benefit of also adding and inferring qualifiers to capture sets are not clear.

not sure if I understood it correctly, does it mean that qualifier inference may not be that useful given the read-only status adaptation?

A related example:

import language.experimental.captureChecking
import caps.*
class Ref extends Mutable:
  private var _data: Int = 0
  def get: Int = _data
  mut def set(x: Int): Unit = _data = x
def test1(a: Ref^{cap.rd}): Unit =
  a.set(42)  // error, as expected
def test2(a: Ref^{cap.rd}): Unit =
  val t: Ref^{cap} = a  // should be error, but ok
  t.set(42)

right now it only has one error. The second but last line widens Ref^{cap.rd} to Ref^{cap} which implicitly lifts the permission of this reference and is clearly unsound. This is the examples that qualifier inference needs to take care of, I guess.

@odersky
Copy link
Contributor Author

odersky commented Jun 9, 2025

not sure if I understood it correctly, does it mean that qualifier inference may not be that useful given the read-only status adaptation?

Good point. Yes, we still need the subtyping / inference rules for soundness. The problem is we cannot rely on them exclusively since then our deep capture sets are wrong. What needs to happen is that we adapt read-only status, which according to subtyping means we widen the type. So unlike for box adaptation this widening is possible (since we still conform to the expected type) but not necessary for conformance (since the original type already conformed). It is however necessary to remember that widened type so that we can reduce deep capture sets and don't get spurious separation errors.

I'll add that part tomorrow.

@odersky odersky force-pushed the adapt-ro branch 2 times, most recently from 15db46d to a096125 Compare June 10, 2025 13:50
odersky added 13 commits June 10, 2025 15:53
Widen to cap.rd only if parent type does not extend Mutable.
We used to have the case where a RetainingType needs to be tested after
a CapturingType. It would be better to reverse that order: If a RetainingType
is possible test this first, and then test a CapturingType. If we just test for
CapturingType we should crash if it's a RetainingType. That way we can express
expectations that a retaining type was transformed.

This commit does the first half: RetainingType unapply now can handle a CapturingType
(and respond with a None). We still need to do the second half, that a CapturingType
unapply does not handle a RetainingType.
 - Also adapt in type arguments, refinements, and function results
 - Also adapt to readonly if target is a mutable, read-only type
@odersky odersky merged commit b35fa8a into scala:main Jun 11, 2025
30 checks passed
@odersky odersky deleted the adapt-ro branch June 11, 2025 17:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:capabilities Issues tied with scala.caps.Capabilities
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants