Skip to content
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

Need a way to require a particular capability to be in a capture set #21313

Closed
odersky opened this issue Aug 1, 2024 · 8 comments
Closed

Need a way to require a particular capability to be in a capture set #21313

odersky opened this issue Aug 1, 2024 · 8 comments
Assignees
Labels
area:experimental:cc Capture checking related cc-experiment Intended to be merged with cc-experiment branch on origin itype:bug

Comments

@odersky
Copy link
Contributor

odersky commented Aug 1, 2024

Minimized example

The following attempt to link capabilities of Async contexts and sources fails.

import language.experimental.captureChecking
import caps.{Capability, CapSet}

trait Async:
  def await[T, Cap >: CapSet^{this} <: CapSet^](src: Source[T, Cap]^): T

trait Source[+T, Cap^]:
  final def await(using ac: Async^{Cap^}) = ac.await[T, Cap](this)```
    // error: Type argument Cap does not conform to lower bound caps.CapSet^{this}

The error makes sense. What we'd need to assert is that also Source.this is in the Cap^ parameter of Source. But we can't express this with a lower bound. This fails:

trait Source[+T, Cap >: CapSet^{this} <: CapSet^]: // error: illegal occurrence of Source.this

Indeed the this of a class cannot be used in the bounds of the class parameters.

I believe we should look for a different way to express this. Maybe a magic type class, caps.Contains?
Something along the lines of:

trait Async:
  def await[T, Cap^](using caps.Contains[Cap, this])(src: Source[T, Cap]^): T

trait Source[+T, Cap^]:
  final def await(using ac: Async^{Cap^}, _: caps.Contains[Cap, this]) = ac.await[T, Cap](this)```
    // error: Type argument Cap does not conform to lower bound caps.CapSet^{this}```
@odersky odersky added stat:needs triage Every issue needs to have an "area" and "itype" label cc-experiment Intended to be merged with cc-experiment branch on origin and removed stat:needs triage Every issue needs to have an "area" and "itype" label labels Aug 1, 2024
@Linyxus
Copy link
Contributor

Linyxus commented Aug 1, 2024

I couldn't see the reason why the Cap of Source has to contain Source.this. Async.await requires its capture argument at least contains Async.this, which is async.this in this case. Given that we have async: Async^{Cap^}, ideally we should be able to show that Async.this <: {Cap^}, and thus pass the subtype check?

@odersky
Copy link
Contributor Author

odersky commented Aug 1, 2024

@Linyxus In fact yes, the error message is misleading.

    // error: Type argument Cap does not conform to lower bound caps.CapSet^{this}

should be

   // error: Type argument Cap does not conform to lower bound caps.CapSet^{ac}

I believe a substitution or asSeenFrom was not done correctly here in the annotation at Typer, which is when the error was issued.

But the problem remains: we can't assure the lower bound in Source.

The variant with Contains should read.

trait Async:
  def await[T, Cap^](using caps.Contains[Cap, this])(src: Source[T, Cap]^): T

trait Source[+T, Cap^]:
  final def await(using ac: Async^{Cap^})(using caps.Contains[Cap, ac]) = ac.await[T, Cap](this)```

@Linyxus
Copy link
Contributor

Linyxus commented Aug 1, 2024

I believe by Contains[Cap, ac] we are asking for the constraint {ac} <: {Cap^}. But, given that ac: Async^{Cap^}, wouldn't {ac} <: {Cap^} already hold?

@odersky
Copy link
Contributor Author

odersky commented Aug 1, 2024

I believe by Contains[Cap, ac] we are asking for the constraint {ac} <: {Cap^}. But, given that ac: Async^{Cap^}, wouldn't {ac} <: {Cap^} already hold?

Good point. We need to verify that. But the original problem is elsewhere. We can't have a lower bound CapSet^{this} in Async.await because that bound cannot be satisfied in Source. That's a test done before capture checking. So I believe we still might want to use Contains but only in Async:

trait Async:
  def await[T, Cap^](using caps.Contains[Cap, this])(src: Source[T, Cap]^): T

trait Source[+T, Cap^]:
  final def await(using ac: Async^{Cap^}) = ac.await[T, Cap](this) // Contains[Cap, ac] is assured because {ac} <: Cap.

@odersky
Copy link
Contributor Author

odersky commented Aug 1, 2024

With Self based type classes we could use a context bound for Contains:

trait Async:
  def await[T, Cap^: Contains[this]])(src: Source[T, Cap]^): T

Then it looks nicer.

@odersky
Copy link
Contributor Author

odersky commented Aug 1, 2024

The idea for Contains is simple. We can put a definition in caps like so:

given Contains[C <: CapSet^, R <: Singleton]()

So Contains instances are always available for typer. Then during capture checking, when we see an argument of type

Contains[Cs, ref.type]

check that {ref} subcaptures Cs.

@Linyxus
Copy link
Contributor

Linyxus commented Aug 1, 2024

I agree, this looks good!

@Gedochao Gedochao added itype:bug area:experimental:cc Capture checking related labels Aug 5, 2024
@odersky
Copy link
Contributor Author

odersky commented Sep 12, 2024

Fixed by #21361

@odersky odersky closed this as completed Sep 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:experimental:cc Capture checking related cc-experiment Intended to be merged with cc-experiment branch on origin itype:bug
Projects
None yet
Development

No branches or pull requests

5 participants