Skip to content

Commit 137d3b9

Browse files
committed
Record uses of this
Was shockingly missing before.
1 parent 668ceb1 commit 137d3b9

File tree

7 files changed

+126
-19
lines changed

7 files changed

+126
-19
lines changed

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -455,7 +455,10 @@ class CheckCaptures extends Recheck, SymTransformer:
455455
markFree(sym, sym.termRef, tree)
456456

457457
def markFree(sym: Symbol, ref: Capability, tree: Tree)(using Context): Unit =
458-
if sym.exists && ref.isTracked then markFree(ref.singletonCaptureSet, tree)
458+
if sym.exists then markFree(ref, tree)
459+
460+
def markFree(ref: Capability, tree: Tree)(using Context): Unit =
461+
if ref.isTracked then markFree(ref.singletonCaptureSet, tree)
459462

460463
/** Make sure the (projected) `cs` is a subset of the capture sets of all enclosing
461464
* environments. At each stage, only include references from `cs` that are outside
@@ -628,25 +631,33 @@ class CheckCaptures extends Recheck, SymTransformer:
628631
// If ident refers to a parameterless method, charge its cv to the environment
629632
includeCallCaptures(sym, sym.info, tree)
630633
else if !sym.isStatic then
631-
// Otherwise charge its symbol, but add all selections and also any `.rd`
632-
// modifier implied by the expected type `pt`.
633-
// Example: If we have `x` and the expected type says we select that with `.a.b`
634-
// where `b` is a read-only method, we charge `x.a.b.rd` instead of `x`.
635-
def addSelects(ref: TermRef, pt: Type): Capability = pt match
636-
case pt: PathSelectionProto if ref.isTracked =>
637-
if pt.sym.isReadOnlyMethod then
638-
ref.readOnly
639-
else
640-
// if `ref` is not tracked then the selection could not give anything new
641-
// class SerializationProxy in stdlib-cc/../LazyListIterable.scala has an example where this matters.
642-
addSelects(ref.select(pt.sym).asInstanceOf[TermRef], pt.pt)
643-
case _ => ref
644-
var pathRef: Capability = addSelects(sym.termRef, pt)
645-
if pathRef.derivesFromMutable && pt.isValueType && !pt.isMutableType then
646-
pathRef = pathRef.readOnly
647-
markFree(sym, pathRef, tree)
634+
markFree(sym, pathRef(sym.termRef, pt), tree)
648635
mapResultRoots(super.recheckIdent(tree, pt), tree.symbol)
649636

637+
override def recheckThis(tree: This, pt: Type)(using Context): Type =
638+
markFree(pathRef(tree.tpe.asInstanceOf[ThisType], pt), tree)
639+
super.recheckThis(tree, pt)
640+
641+
/** Add all selections and also any `.rd modifier implied by the expected
642+
* type `pt` to `base`. Example:
643+
* If we have `x` and the expected type says we select that with `.a.b`
644+
* where `b` is a read-only method, we charge `x.a.b.rd` instead of `x`.
645+
*/
646+
private def pathRef(base: TermRef | ThisType, pt: Type)(using Context): Capability =
647+
def addSelects(ref: TermRef | ThisType, pt: Type): Capability = pt match
648+
case pt: PathSelectionProto if ref.isTracked =>
649+
if pt.sym.isReadOnlyMethod then
650+
ref.readOnly
651+
else
652+
// if `ref` is not tracked then the selection could not give anything new
653+
// class SerializationProxy in stdlib-cc/../LazyListIterable.scala has an example where this matters.
654+
addSelects(ref.select(pt.sym).asInstanceOf[TermRef], pt.pt)
655+
case _ => ref
656+
val ref: Capability = addSelects(base, pt)
657+
if ref.derivesFromMutable && pt.isValueType && !pt.isMutableType
658+
then ref.readOnly
659+
else ref
660+
650661
/** The expected type for the qualifier of a selection. If the selection
651662
* could be part of a capability path or is a a read-only method, we return
652663
* a PathSelectionProto.

compiler/src/dotty/tools/dotc/transform/Recheck.scala

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,12 @@ abstract class Recheck extends Phase, SymTransformer:
248248
def recheckSelection(tree: Select, qualType: Type, name: Name, pt: Type)(using Context): Type =
249249
recheckSelection(tree, qualType, name, sharpen = identity[Denotation])
250250

251+
def recheckThis(tree: This, pt: Type)(using Context): Type =
252+
tree.tpe
253+
254+
def recheckSuper(tree: Super, pt: Type)(using Context): Type =
255+
tree.tpe
256+
251257
def recheckBind(tree: Bind, pt: Type)(using Context): Type = tree match
252258
case Bind(name, body) =>
253259
recheck(body, pt)
@@ -540,7 +546,9 @@ abstract class Recheck extends Phase, SymTransformer:
540546
def recheckUnnamed(tree: Tree, pt: Type): Type = tree match
541547
case tree: Apply => recheckApply(tree, pt)
542548
case tree: TypeApply => recheckTypeApply(tree, pt)
543-
case _: New | _: This | _: Super | _: Literal => tree.tpe
549+
case tree: This => recheckThis(tree, pt)
550+
case tree: Super => recheckSuper(tree, pt)
551+
case _: New | _: Literal => tree.tpe
544552
case tree: Typed => recheckTyped(tree)
545553
case tree: Assign => recheckAssign(tree)
546554
case tree: Block => recheckBlock(tree, pt)
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/class-caps.scala:18:46 -----------------------------------
2+
18 | def addWritesToConsole: (Int, Int) -> Int = (a, b) => // error
3+
| ^
4+
| Found: (a: Int, b: Int) ->{Test.this.console} Int
5+
| Required: (Int, Int) -> Int
6+
|
7+
| Note that capability Test.this.console is not included in capture set {}.
8+
19 | log(s"adding a ($a) to b ($b)")(using console)
9+
20 | a + b
10+
|
11+
| longer explanation available when compiling with `-explain`
12+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/class-caps.scala:28:46 -----------------------------------
13+
28 | def addWritesToConsole: (Int, Int) -> Int = (a, b) => // error
14+
| ^
15+
| Found: (a: Int, b: Int) ->{Test1.this.console} Int
16+
| Required: (Int, Int) -> Int
17+
|
18+
| Note that capability Test1.this.console is not included in capture set {}.
19+
29 | log(s"adding a ($a) to b ($b)")(using console)
20+
30 | a + b
21+
|
22+
| longer explanation available when compiling with `-explain`
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
//> using scala 3.nightly
2+
3+
import scala.language.experimental.captureChecking
4+
import scala.caps.*
5+
6+
class Console() extends SharedCapability:
7+
def println(s: String): Unit = System.out.println(s"console: $s")
8+
9+
def log(s: String)(using c: Console): Unit =
10+
summon[Console].println(s)
11+
12+
class Test:
13+
this: Test^ =>
14+
val addPure: (Int, Int) -> Int = (a, b) => a + b // same as before
15+
16+
val console: Console^ = Console() // provide capability locally
17+
18+
def addWritesToConsole: (Int, Int) -> Int = (a, b) => // error
19+
log(s"adding a ($a) to b ($b)")(using console)
20+
a + b
21+
22+
23+
class Test1:
24+
val addPure: (Int, Int) -> Int = (a, b) => a + b // same as before
25+
26+
val console: Console^ = Console() // provide capability locally
27+
28+
def addWritesToConsole: (Int, Int) -> Int = (a, b) => // error
29+
log(s"adding a ($a) to b ($b)")(using console)
30+
a + b
31+
32+
object Test2:
33+
val addPure: (Int, Int) -> Int = (a, b) => a + b // same as before
34+
35+
val console: Console^ = Console() // provide capability locally
36+
37+
def addWritesToConsole: (Int, Int) -> Int = (a, b) => // ok since `console` is static (maybe flag this?)
38+
log(s"adding a ($a) to b ($b)")(using console)
39+
a + b
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
abstract class Test:
2+
this: Test^ =>
3+
def outer: Int
4+
5+
class Inner(x: Int):
6+
def this() = this(outer)
7+
8+
val i = Inner()
9+
val _: Inner = i
10+
11+
val f = () => Inner()
12+
val _: () ->{this} Inner = f
13+

tests/pos-custom-args/captures/nested-classes.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ class IO extends caps.SharedCapability
55
class Blah
66
class Pkg(using io: IO):
77
class Foo:
8+
this: Foo^{Pkg.this} =>
89
def m(foo: Blah^{io}) = ???
910
class Pkg2(using io: IO):
1011
class Foo:
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package scala
2+
package collection
3+
package immutable
4+
5+
final class LazyListIterable[+A]():
6+
this: LazyListIterable[A]^ =>
7+
var _head: Any = 0
8+
private def this(head: A, tail: LazyListIterable[A]^) =
9+
this()
10+
_head = head
11+
12+
object LazyListIterable:
13+
@inline private def eagerCons[A](hd: A, tl: LazyListIterable[A]^): LazyListIterable[A]^{tl} = new LazyListIterable[A](hd, tl)

0 commit comments

Comments
 (0)