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 capture checking the compiler as a test #16292

Merged
merged 38 commits into from
Nov 10, 2022
Merged

Conversation

odersky
Copy link
Contributor

@odersky odersky commented Nov 7, 2022

  • Copy compiler code base into a separate test
  • Adapt code base so that it can be capture checked

Based on #16254

Only the last two commits are new:

  • 57527c4 copies the compiler codebase as a separate test.
  • 6dfeaa7 makes the changes so that it passes capture checking.

The crash can happen if we compile some modules without capture checking when
other modules are compiled in the same run with capture checking.
Often, the box point is more interesting than the unbox point. It's when unsafe boxing
a variable's RHS that we have to make usre manually that no capability escapes.
They take a `{*} Object` instead of an `Object` parameter.
…le definition

We now allow an inferred type of the form `{C.this} T` if `C` is a pure class.
This is OK since such a type is equivalent to `T`.
An inferred self type in a non-sealed class is now OK if it is the same
as an explicit self type in a base class.
Add a case for cases like

    x.type <:< {r} (A | x.type)

where `x` captures `r`.

# Conflicts:
#	tests/pos-with-compiler-cc/dotc/core/Types.scala
Impure exceptions can't be thrown anyway since `throw` takes a
pure `Throwable` as operand.

The change avoids having to declare explicit pure self types for
exception classes.
When reducing match types, we did not permit to widen an abstract type scrutinee together with
a non-unique type parameter instantiation. But this criterion need not apply if the
widening is in a constraint satisfiabilty check. For instance

    T <: Tuple
    X <: A *: T

    X match
      case x *: (xs <: Tuple) => ...

Here the match does not succeed since X needs to be widened to A *: T and the x parameter
cannot be uniquely instantiated. On the other hand

    A *: T match
      case x *: (xs <: Tuple) => ...

should pass since there is no widening of the scrutinee. But there is a test T <: Tuple to check
whether T can match xs. That second check should not fall under the canWidenAbstract criterion.

Note: I was not able to make a test (without the fix) fail without capture checking, maybe
because the syntax (xs <: Tuple) is actually not allowed. But it is generated after Typer.
We previously required that a class inheriting an @experimental class is itself
@experimental. This is too strict. It should be OK to have the inheriting class
be contained in an experimental scope. I.e.
```
@experimental class A
@experimental object o:
  class B extends A
```
is now OK.
This can replace the more obscure idiom of declaring a
non-capturing self type.
Since root addition handlers are stored in capture sets they should
not close over a context, since otherwise every capture set would
close over a context.

# Conflicts:
#	tests/pos-with-compiler-cc/dotc/cc/CaptureSet.scala
 - Avoid accidental addition of capture set variables through traverseChildren
   of exact ValDefs and DefDefs.
 - Don't require that overriding symbols need explicit types if their capture set
   is non-empty. This is not needed since an overriding symbol with inferred type
   will get the type of the overridden symbol.
We need some special treatment for types such as `(=> A) => B`
Make sure to restore info of anonymous functions and their parameters after cc
…irements

Default getters always have an inferred result type. If that type captures capabilities,
we used to get an error that an inferred non pure type needs to be explicitly defined.
But usually, a default getter type does not matter. So we make an exception and allow
it. Note that this could be a hole, for instance in a situation like this:
```scala
  def f[X](x: X = foo): X = ...
```
If `foo`'s inferred type is `{*} T`, and there is a call to `f()` in the same compilation
unit `X` will be instantiated to `{*} T` and this will also be the result for `f`. But if
the call is in a different compilation unit, it will only see `foo: T`, and the result of
`f` is typed `T`.

A better solution would be to demand that default getter's types are pure, but only if the
getter type can leak into the type of the function to which it belongs. The problem is that
currently that's very hard to do because when we see a default getter it's difficult to
discover to which parameter from which function it belongs.

Therefore, for the moment we leave the hole open, hoping for a better solution to default getters
in the future.
It was already implied before, but so far an explicit rule that

    cs T <: Any

was missing. Adding that rule is important for bounds checking.
It turns out that bounds checking was missing so far.
Two major changes:

 1. During setup, don't generate a capture set variable for a type T if
   one of the base types of `T` is a pure class.

 2. During subtype checking, admit `cs T <: T` if one of the base types
   of `T` is a pure class. The idea is that in this case we know that the
   capture set will always be empty at runtime.

But don't do (2.) if we are checking self types. In this case the capture set cannot be
stripped. Example:

    class A:
      this: A =>
    class B extends A
      this: {c} B =>

In this case we should not assume `{c} B <: B` since we need to flag an error that the self
type of B does not conform to the self type of A.

   are checking self types. In that cas
We need an unsafe transform from

    {*} A => B

to

    box {*} A => B

I don't think this is expressible with `unsafeBox/unsafeUnbox` alone.
…e cc

This is needed to make -Ycheck work after capture checking
Add compiler as a separate test to be modified so that the compiler sources
can be capture checked.
Drop recheck test to make up for some of the overhead due to the
capture checking test.
odersky added a commit that referenced this pull request Nov 10, 2022
This PR contains all fixes and enhancements that were needed to make the
compiler itself pass capture checking.

A separate PR #16292 adds a capture-checkable version of the compiler as
a test case.
@odersky odersky merged commit d8a6751 into scala:main Nov 10, 2022
@odersky odersky deleted the test-cc branch November 10, 2022 14:53
@Kordyjan Kordyjan added this to the 3.3.0 milestone Aug 1, 2023
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.

3 participants