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

Add path support for capture checking #21445

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

noti0na1
Copy link
Member

No description provided.

compiler/src/dotty/tools/dotc/parsing/Parsers.scala Outdated Show resolved Hide resolved
compiler/src/dotty/tools/dotc/ast/untpd.scala Outdated Show resolved Hide resolved
@@ -122,10 +122,6 @@ object CheckCaptures:
* This check is performed at Typer.
*/
def checkWellformed(parent: Tree, ann: Tree)(using Context): Unit =
Copy link
Contributor

Choose a reason for hiding this comment

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

Why was this check removed?

Copy link
Member Author

Choose a reason for hiding this comment

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

When implementing the path, I considered the rule for a singleton type with a capture set. I don't think this check is necessary, as we can already achieve the same result by creating a type alias with a capture set.

Copy link
Contributor

Choose a reason for hiding this comment

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

So you are saying the rule is incomplete? But then maybe we should dealias before we check? What is the argument for removing it?

Copy link
Member Author

Choose a reason for hiding this comment

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

What I mean is it is okey to let users add a capture set to a singleton type, if they just want to explicitly indicate what the underlying type capturing (for example, in the member function signature def f: this.file^{this.io}).

compiler/src/dotty/tools/dotc/cc/CaptureOps.scala Outdated Show resolved Hide resolved
@@ -99,25 +100,56 @@ trait CaptureRef extends TypeProxy, ValueType:
* x: x1.type /\ x1 subsumes y ==> x subsumes y
*/
final def subsumes(y: CaptureRef)(using Context): Boolean =
Copy link
Contributor

@odersky odersky Sep 24, 2024

Choose a reason for hiding this comment

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

We need to get better reassurance that this does the right thing. It looks too complicated to be able to argue it's obviously correct. What would help:

  • A formal set of rules for subcapturing.
  • A rendition of these rules as a doc comment for this method. It's a shame that the old doc comment was not updated in any way.
  • More extensive tests that try to test all combinations. I saw we have neg tests but no new pos tests.

@odersky
Copy link
Contributor

odersky commented Sep 24, 2024

I tried this version of the old subsumes and compared its outcome with the new subsumes for all the tests in testCompilation.

  final def subsumesOld(y: CaptureRef)(using Context): Boolean =
    (this eq y)
    || this.isRootCapability
    || y.match
        case y: TermRef =>
            y.prefix.match
              case ypre: CaptureRef =>
                this.subsumes(ypre)
                || this.match
                    case x @ TermRef(xpre: CaptureRef, _) =>
                      x.symbol == y.symbol && xpre =:= ypre
                    case _ =>
                      false
              case _ => false
          || y.info.match
              case y1: SingletonCaptureRef => this.subsumes(y1)
              case _ => false
        case MaybeCapability(y1) => this.stripMaybe.subsumes(y1)
        case _ => false
    || this.match
        case ReachCapability(x1) => x1.subsumes(y.stripReach)
        case x: TermRef =>
          x.info match
            case x1: SingletonCaptureRef => x1.subsumes(y)
            case _ => false
        case x: TermParamRef => subsumesExistentially(x, y)
        case x: TypeRef => assumedContainsOf(x).contains(y)
        case _ => false

There was no difference in outcomes, and the version here is shorter and clearer. So we should probably use this, unless we have tests that show there is a difference and the version here is wrong.

@odersky
Copy link
Contributor

odersky commented Sep 24, 2024

I would have expected more changes in the recheckSelect rule. Indeed, recheckSelect seems to not make use of path types. Here is a simple test program:

class IO

class C(val f: IO^)

def test(io: IO^) =
  val c = C(io)
  val g = () => println(c.f)

This types g as () ->{c} Unit. But it should be () ->{c.f} Unit.

EDIT: We probably also need to change markFree, so that it stores path types instead of their prefixes. The problem seems to be that we already marlk c free when we see it in c.f. We should recognize that the expected type is a SelectionProto and hold off until we have typechecked the full path.

@odersky
Copy link
Contributor

odersky commented Sep 24, 2024

Also, I tink in light of path types, there's scope for a simplification of recheckSelect. A lot of the contortions there were made to make up for the lack of path types.

@odersky
Copy link
Contributor

odersky commented Sep 24, 2024

Generally, what's missing is a suite of tests that shows how path-dependent types give a more expressive language. Show code that did not typecheck before, but now does.

noti0na1 and others added 6 commits September 25, 2024 21:45
This is done for comparing old with new
Add the path cases without changing the whole logic
If we refer to a path `a.b`, we should mark `a.b` as used,
which is better than marking `a`.
Needed to make stdlib2-cc go through.

There were two errors. One in LayListIterable required a type annotation
and a tweak to markFree. The other in Vieew.scala required a cast, but this could be fixed
with better handling of pattern matching. path-patmat-should-be-pos.scala is a minimization.
@odersky
Copy link
Contributor

odersky commented Sep 26, 2024

There's a problem with pattern matching shown in the path-patmat-should-be-pos.scala test.

Comment on lines 113 to 114
case info: AndType => test(info.tp1) || test(info.tp2)
case info: OrType => test(info.tp1) && test(info.tp2)
Copy link
Member Author

Choose a reason for hiding this comment

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

We can recur on left and right tress as well.

this.subsumes(ypre)
|| this.match
case x @ TermRef(xpre: CaptureRef, _) if x.symbol == y.symbol =>
subsumingRefs(xpre, ypre) && subsumingRefs(ypre, xpre)
Copy link
Member Author

Choose a reason for hiding this comment

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

If x.symbol == y.symbol, then x.prefix must equivalent to y.prefix in order to show x subsumes y. Is it simpler to just use TypeCompare to check them? I think we have to prove (x subsumes y) && (y subsumes x) ==> x =:= y.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants