Skip to content

Commit c9a275c

Browse files
authored
Merge pull request #13657 from dotty-staging/this-cc-in-members
Special rule for `{this}` in capture sets of class members
2 parents 850d1c1 + 6fc8c98 commit c9a275c

File tree

10 files changed

+276
-3
lines changed

10 files changed

+276
-3
lines changed

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

+8-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,14 @@ sealed abstract class CaptureSet extends Showable:
4949
/** Is this capture set definitely non-empty? */
5050
final def isNotEmpty: Boolean = !elems.isEmpty
5151

52-
/** Cast to variable. @pre: @isConst */
52+
/** Cast to Const. @pre: isConst */
53+
def asConst: Const = this match
54+
case c: Const => c
55+
case v: Var =>
56+
assert(v.isConst)
57+
Const(v.elems)
58+
59+
/** Cast to variable. @pre: !isConst */
5360
def asVar: Var =
5461
assert(!isConst)
5562
asInstanceOf[Var]

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

+5-2
Original file line numberDiff line numberDiff line change
@@ -196,12 +196,15 @@ abstract class Recheck extends Phase, IdentityDenotTransformer:
196196
bindType
197197

198198
def recheckValDef(tree: ValDef, sym: Symbol)(using Context): Unit =
199-
if !tree.rhs.isEmpty then recheck(tree.rhs, sym.info)
199+
if !tree.rhs.isEmpty then recheckRHS(tree.rhs, sym.info, sym)
200200

201201
def recheckDefDef(tree: DefDef, sym: Symbol)(using Context): Unit =
202202
val rhsCtx = linkConstructorParams(sym).withOwner(sym)
203203
if !tree.rhs.isEmpty && !sym.isInlineMethod && !sym.isEffectivelyErased then
204-
inContext(rhsCtx) { recheck(tree.rhs, recheck(tree.tpt)) }
204+
inContext(rhsCtx) { recheckRHS(tree.rhs, recheck(tree.tpt), sym) }
205+
206+
def recheckRHS(tree: Tree, pt: Type, sym: Symbol)(using Context): Type =
207+
recheck(tree, pt)
205208

206209
def recheckTypeDef(tree: TypeDef, sym: Symbol)(using Context): Type =
207210
recheck(tree.rhs)

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

+16
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,22 @@ class CheckCaptures extends Recheck:
322322
interpolateVarsIn(tree.tpt)
323323
curEnv = saved
324324

325+
override def recheckRHS(tree: Tree, pt: Type, sym: Symbol)(using Context): Type =
326+
val pt1 = pt match
327+
case CapturingType(core, refs, _)
328+
if sym.owner.isClass && !sym.owner.isExtensibleClass
329+
&& refs.elems.contains(sym.owner.thisType) =>
330+
val paramCaptures =
331+
sym.paramSymss.flatten.foldLeft(CaptureSet.empty) { (cs, p) =>
332+
val pcs = p.info.captureSet
333+
(cs ++ (if pcs.isConst then pcs else CaptureSet.universal)).asConst
334+
}
335+
val declaredCaptures = sym.owner.asClass.givenSelfType.captureSet
336+
pt.derivedCapturingType(core, refs ++ (declaredCaptures -- paramCaptures))
337+
case _ =>
338+
pt
339+
recheck(tree, pt1)
340+
325341
override def recheckClassDef(tree: TypeDef, impl: Template, cls: ClassSymbol)(using Context): Type =
326342
for param <- cls.paramGetters do
327343
if param.is(Private) && !param.info.captureSet.isAlwaysEmpty then
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists1.scala:25:63 -----------------------------------
2+
25 | def concat(other: {f} LazyList[A]): {this} LazyList[A] = ??? : ({xs, f} LazyList[A]) // error
3+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
| Found: {xs, f} LazyList[A]
5+
| Required: {Mapped.this, xs} LazyList[A]
6+
7+
longer explanation available when compiling with `-explain`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class CC
2+
type Cap = {*} CC
3+
4+
trait LazyList[+A]:
5+
this: ({*} LazyList[A]) =>
6+
7+
def isEmpty: Boolean
8+
def head: A
9+
def tail: {this} LazyList[A]
10+
11+
object LazyNil extends LazyList[Nothing]:
12+
def isEmpty: Boolean = true
13+
def head = ???
14+
def tail = ???
15+
16+
extension [A](xs: {*} LazyList[A])
17+
def map[B](f: {*} A => B): {xs, f} LazyList[B] =
18+
final class Mapped extends LazyList[B]:
19+
this: ({xs, f} Mapped) =>
20+
21+
def isEmpty = false
22+
def head: B = f(xs.head)
23+
def tail: {this} LazyList[B] = xs.tail.map(f) // OK
24+
def drop(n: Int): {this} LazyList[B] = ??? : ({xs, f} LazyList[B]) // OK
25+
def concat(other: {f} LazyList[A]): {this} LazyList[A] = ??? : ({xs, f} LazyList[A]) // error
26+
new Mapped
27+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
-- [E163] Declaration Error: tests/neg-custom-args/captures/lazylists2.scala:50:10 -------------------------------------
2+
50 | def tail: {xs, f} LazyList[B] = xs.tail.map(f) // error
3+
| ^
4+
| error overriding method tail in trait LazyList of type => {Mapped.this} LazyList[B];
5+
| method tail of type => {xs, f} LazyList[B] has incompatible type
6+
7+
longer explanation available when compiling with `-explain`
8+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:18:4 ------------------------------------
9+
18 | final class Mapped extends LazyList[B]: // error
10+
| ^
11+
| Found: {f, xs} LazyList[B]
12+
| Required: {f} LazyList[B]
13+
19 | this: ({xs, f} Mapped) =>
14+
20 | def isEmpty = false
15+
21 | def head: B = f(xs.head)
16+
22 | def tail: {this} LazyList[B] = xs.tail.map(f)
17+
23 | new Mapped
18+
19+
longer explanation available when compiling with `-explain`
20+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:27:4 ------------------------------------
21+
27 | final class Mapped extends LazyList[B]: // error
22+
| ^
23+
| Found: {f, xs} LazyList[B]
24+
| Required: {xs} LazyList[B]
25+
28 | this: ({xs, f} Mapped) =>
26+
29 | def isEmpty = false
27+
30 | def head: B = f(xs.head)
28+
31 | def tail: {this} LazyList[B] = xs.tail.map(f)
29+
32 | new Mapped
30+
31+
longer explanation available when compiling with `-explain`
32+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:41:48 -----------------------------------
33+
41 | def tail: {this} LazyList[B] = xs.tail.map(f) // error
34+
| ^^^^^^^^^^^^^^
35+
| Found: {f} LazyList[B]
36+
| Required: {xs} LazyList[B]
37+
38+
longer explanation available when compiling with `-explain`
39+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/lazylists2.scala:59:48 -----------------------------------
40+
59 | def tail: {this} LazyList[B] = xs.tail.map(f) // error
41+
| ^^^^^^^^^^^^^^
42+
| Found: {f} LazyList[B]
43+
| Required: {Mapped.this} LazyList[B]
44+
45+
longer explanation available when compiling with `-explain`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
class CC
2+
type Cap = {*} CC
3+
4+
trait LazyList[+A]:
5+
this: ({*} LazyList[A]) =>
6+
7+
def isEmpty: Boolean
8+
def head: A
9+
def tail: {this} LazyList[A]
10+
11+
object LazyNil extends LazyList[Nothing]:
12+
def isEmpty: Boolean = true
13+
def head = ???
14+
def tail = ???
15+
16+
extension [A](xs: {*} LazyList[A])
17+
def map[B](f: {*} A => B): {f} LazyList[B] =
18+
final class Mapped extends LazyList[B]: // error
19+
this: ({xs, f} Mapped) =>
20+
21+
def isEmpty = false
22+
def head: B = f(xs.head)
23+
def tail: {this} LazyList[B] = xs.tail.map(f)
24+
new Mapped
25+
26+
def map2[B](f: {*} A => B): {xs} LazyList[B] =
27+
final class Mapped extends LazyList[B]: // error
28+
this: ({xs, f} Mapped) =>
29+
30+
def isEmpty = false
31+
def head: B = f(xs.head)
32+
def tail: {this} LazyList[B] = xs.tail.map(f)
33+
new Mapped
34+
35+
def map3[B](f: {*} A => B): {xs} LazyList[B] =
36+
final class Mapped extends LazyList[B]:
37+
this: ({xs} Mapped) =>
38+
39+
def isEmpty = false
40+
def head: B = f(xs.head)
41+
def tail: {this} LazyList[B] = xs.tail.map(f) // error
42+
new Mapped
43+
44+
def map4[B](f: {*} A => B): {xs} LazyList[B] =
45+
final class Mapped extends LazyList[B]:
46+
this: ({xs, f} Mapped) =>
47+
48+
def isEmpty = false
49+
def head: B = f(xs.head)
50+
def tail: {xs, f} LazyList[B] = xs.tail.map(f) // error
51+
new Mapped
52+
53+
def map5[B](f: {*} A => B): LazyList[B] =
54+
class Mapped extends LazyList[B]:
55+
this: ({xs, f} Mapped) =>
56+
57+
def isEmpty = false
58+
def head: B = f(xs.head)
59+
def tail: {this} LazyList[B] = xs.tail.map(f) // error
60+
class Mapped2 extends Mapped:
61+
this: Mapped =>
62+
new Mapped2
63+
64+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
class CC
2+
type Cap = {*} CC
3+
4+
//-------------------------------------------------
5+
6+
def test(E: Cap) =
7+
8+
trait LazyList[+A]:
9+
protected def contents: {E} () => (A, {E} LazyList[A])
10+
def isEmpty: Boolean
11+
def head: A = contents()._1
12+
def tail: {E} LazyList[A] = contents()._2
13+
14+
class LazyCons[+A](override val contents: {E} () => (A, {E} LazyList[A]))
15+
extends LazyList[A]:
16+
def isEmpty: Boolean = false
17+
18+
object LazyNil extends LazyList[Nothing]:
19+
def contents: {E} () => (Nothing, LazyList[Nothing]) = ???
20+
def isEmpty: Boolean = true
21+
22+
extension [A](xs: {E} LazyList[A])
23+
def map[B](f: {E} A => B): {E} LazyList[B] =
24+
if xs.isEmpty then LazyNil
25+
else
26+
val cons = () => (f(xs.head), xs.tail.map(f))
27+
LazyCons(cons)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
class CC
2+
type Cap = {*} CC
3+
4+
trait LazyList[+A]:
5+
this: ({*} LazyList[A]) =>
6+
7+
def isEmpty: Boolean
8+
def head: A
9+
def tail: {this} LazyList[A]
10+
11+
object LazyNil extends LazyList[Nothing]:
12+
def isEmpty: Boolean = true
13+
def head = ???
14+
def tail = ???
15+
16+
extension [A](xs: {*} LazyList[A])
17+
def map[B](f: {*} A => B): {xs, f} LazyList[B] =
18+
final class Mapped extends LazyList[B]:
19+
this: ({xs, f} Mapped) =>
20+
21+
def isEmpty = false
22+
def head: B = f(xs.head)
23+
def tail: {this} LazyList[B] = xs.tail.map(f) // OK
24+
def concat(other: {f} LazyList[A]): {this, f} LazyList[A] = ??? : ({xs, f} LazyList[A]) // OK
25+
if xs.isEmpty then LazyNil
26+
else new Mapped
27+
28+
def test(cap1: Cap, cap2: Cap) =
29+
def f(x: String): String = if cap1 == cap1 then "" else "a"
30+
def g(x: String): String = if cap2 == cap2 then "" else "a"
31+
32+
val xs =
33+
class Initial extends LazyList[String]:
34+
this: ({cap1} Initial) =>
35+
36+
def isEmpty = false
37+
def head = f("")
38+
def tail = LazyNil
39+
new Initial
40+
val xsc: {cap1} LazyList[String] = xs
41+
val ys = xs.map(g)
42+
val ysc: {cap1, cap2} LazyList[String] = ys
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
class CC
2+
type Cap = {*} CC
3+
4+
trait LazyList[+A]:
5+
this: ({*} LazyList[A]) =>
6+
7+
def isEmpty: Boolean
8+
def head: A
9+
def tail: {this} LazyList[A]
10+
11+
object LazyNil extends LazyList[Nothing]:
12+
def isEmpty: Boolean = true
13+
def head = ???
14+
def tail = ???
15+
16+
final class LazyCons[+T](val x: T, val xs: {*} () => {*} LazyList[T]) extends LazyList[T]:
17+
this: ({*} LazyList[T]) =>
18+
19+
def isEmpty = false
20+
def head = x
21+
def tail: {this} LazyList[T] = xs()
22+
23+
extension [A](xs: {*} LazyList[A])
24+
def map[B](f: {*} A => B): {xs, f} LazyList[B] =
25+
if xs.isEmpty then LazyNil
26+
else LazyCons(f(xs.head), () => xs.tail.map(f))
27+
28+
def test(cap1: Cap, cap2: Cap) =
29+
def f(x: String): String = if cap1 == cap1 then "" else "a"
30+
def g(x: String): String = if cap2 == cap2 then "" else "a"
31+
32+
val xs = LazyCons("", () => if f("") == f("") then LazyNil else LazyNil)
33+
val xsc: {cap1} LazyList[String] = xs
34+
val ys = xs.map(g)
35+
val ysc: {cap1, cap2} LazyList[String] = ys

0 commit comments

Comments
 (0)