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

Merged
merged 12 commits into from
Oct 30, 2024
Merged

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.

@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.

@noti0na1 noti0na1 requested a review from odersky October 11, 2024 06:53
Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

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

Otherwise LGTM

@odersky odersky removed their assignment Oct 27, 2024
noti0na1 and others added 8 commits October 27, 2024 19:33
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 Oct 27, 2024

Rebased to main

Copy link
Contributor

@odersky odersky left a comment

Choose a reason for hiding this comment

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

Great! I think we can merge this now.

@odersky odersky merged commit fa85416 into scala:main Oct 30, 2024
29 checks passed
@odersky odersky deleted the cc-path branch October 30, 2024 10:04
@bracevac bracevac added the cc-experiment Intended to be merged with cc-experiment branch on origin label Oct 31, 2024
@WojciechMazur WojciechMazur added this to the 3.6.3 milestone Dec 9, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cc-experiment Intended to be merged with cc-experiment branch on origin
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants