Skip to content

Commit 0800dec

Browse files
authored
Disallow covariant caps in the lower bound of type members (#19624)
Fixes #19330. Since when instantiating a type member we do not disallow covariant `cap`s in the instance, a check is added at the application site to check for covariant `cap`s in the lower bound of type members to maintain soundness. This check is elided for type parameters since their instances are always checked at the instantiation site.
2 parents 50d62f7 + d539d89 commit 0800dec

File tree

4 files changed

+55
-20
lines changed

4 files changed

+55
-20
lines changed

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

+7-20
Original file line numberDiff line numberDiff line change
@@ -142,32 +142,19 @@ object CheckCaptures:
142142

143143
private val seen = new EqHashSet[TypeRef]
144144

145-
/** Check that there is at least one method containing carrier and defined
146-
* in the scope of tparam. E.g. this is OK:
147-
* def f[T] = { ... var x: T ... }
148-
* So is this:
149-
* class C[T] { def f() = { class D { var x: T }}}
150-
* But this is not OK:
151-
* class C[T] { object o { var x: T }}
152-
*/
153-
extension (tparam: Symbol) def isParametricIn(carrier: Symbol): Boolean =
154-
carrier.exists && {
155-
val encl = carrier.owner.enclosingMethodOrClass
156-
if encl.isClass then tparam.isParametricIn(encl)
157-
else
158-
def recur(encl: Symbol): Boolean =
159-
if tparam.owner == encl then true
160-
else if encl.isStatic || !encl.exists then false
161-
else recur(encl.owner.enclosingMethodOrClass)
162-
recur(encl)
163-
}
164-
165145
def traverse(t: Type) =
166146
t.dealiasKeepAnnots match
167147
case t: TypeRef =>
168148
if !seen.contains(t) then
169149
seen += t
170150
traverseChildren(t)
151+
152+
// Check the lower bound of path dependent types.
153+
// See issue #19330.
154+
val isMember = t.prefix ne NoPrefix
155+
t.info match
156+
case TypeBounds(lo, _) if isMember => traverse(lo)
157+
case _ =>
171158
case AnnotatedType(_, ann) if ann.symbol == defn.UncheckedCapturesAnnot =>
172159
()
173160
case t =>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import language.experimental.captureChecking
2+
3+
trait Logger
4+
def usingLogger[T](op: Logger^ => T): T = ???
5+
6+
def foo[T >: () => Logger^](): T =
7+
val leaked = usingLogger[T]: l => // ok
8+
val t: () => Logger^ = () => l
9+
t: T
10+
leaked
11+
12+
def test(): Unit =
13+
val bad: () => Logger^ = foo[() => Logger^] // error
14+
val leaked: Logger^ = bad() // leaked scoped capability!
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import language.experimental.captureChecking
2+
3+
trait Logger
4+
def usingLogger[T](op: Logger^ => T): T = ???
5+
6+
trait Foo:
7+
type T >: () => Logger^
8+
9+
def foo: this.T =
10+
val leaked = usingLogger[T]: l => // error
11+
val t: () => Logger^ = () => l
12+
t: T
13+
leaked
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import language.experimental.captureChecking
2+
3+
trait Logger
4+
def usingLogger[T](op: Logger^ => T): T = ???
5+
6+
trait Foo:
7+
type T >: () => Logger^
8+
9+
class Bar extends Foo:
10+
type T = () => Logger^
11+
12+
def foo(x: Foo): x.T =
13+
val leaked = usingLogger[x.T]: l => // error
14+
val t: () => Logger^ = () => l
15+
t: x.T
16+
leaked
17+
18+
def test(): Unit =
19+
val bar = new Bar
20+
val bad: bar.T = foo(bar)
21+
val leaked: Logger^ = bad() // leaked scoped capability!

0 commit comments

Comments
 (0)