From e6765088a4041e2541b2a60a7d20ad8636030d2b Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Tue, 6 Feb 2024 14:00:36 +0800 Subject: [PATCH 1/4] Disallow covariant `cap`s in the lower bound of type members --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 7 +++++++ .../neg-custom-args/captures/i19330-alt.scala | 14 +++++++++++++ .../captures/i19330-alt2.scala | 13 ++++++++++++ tests/neg-custom-args/captures/i19330.scala | 21 +++++++++++++++++++ 4 files changed, 55 insertions(+) create mode 100644 tests/neg-custom-args/captures/i19330-alt.scala create mode 100644 tests/neg-custom-args/captures/i19330-alt2.scala create mode 100644 tests/neg-custom-args/captures/i19330.scala diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 4564bed6db01..419e6454369c 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -168,6 +168,13 @@ object CheckCaptures: if !seen.contains(t) then seen += t traverseChildren(t) + + // Check the lower bound of path dependent types. + // See issue #19330. + val isTypeParam = t.prefix eq NoPrefix + t.info match + case TypeBounds(lo, hi) if !isTypeParam => traverse(lo) + case _ => case AnnotatedType(_, ann) if ann.symbol == defn.UncheckedCapturesAnnot => () case t => diff --git a/tests/neg-custom-args/captures/i19330-alt.scala b/tests/neg-custom-args/captures/i19330-alt.scala new file mode 100644 index 000000000000..8e6488cb9ccc --- /dev/null +++ b/tests/neg-custom-args/captures/i19330-alt.scala @@ -0,0 +1,14 @@ +import language.experimental.captureChecking + +trait Logger +def usingLogger[T](op: Logger^ => T): T = ??? + +def foo[T >: () => Logger^](): T = + val leaked = usingLogger[T]: l => // ok + val t: () => Logger^ = () => l + t: T + leaked + +def test(): Unit = + val bad: () => Logger^ = foo[() => Logger^] // error + val leaked: Logger^ = bad() // leaked scoped capability! diff --git a/tests/neg-custom-args/captures/i19330-alt2.scala b/tests/neg-custom-args/captures/i19330-alt2.scala new file mode 100644 index 000000000000..b49dce4b71ef --- /dev/null +++ b/tests/neg-custom-args/captures/i19330-alt2.scala @@ -0,0 +1,13 @@ +import language.experimental.captureChecking + +trait Logger +def usingLogger[T](op: Logger^ => T): T = ??? + +trait Foo: + type T >: () => Logger^ + + def foo: this.T = + val leaked = usingLogger[T]: l => // error + val t: () => Logger^ = () => l + t: T + leaked diff --git a/tests/neg-custom-args/captures/i19330.scala b/tests/neg-custom-args/captures/i19330.scala new file mode 100644 index 000000000000..8acb0dd8f66b --- /dev/null +++ b/tests/neg-custom-args/captures/i19330.scala @@ -0,0 +1,21 @@ +import language.experimental.captureChecking + +trait Logger +def usingLogger[T](op: Logger^ => T): T = ??? + +trait Foo: + type T >: () => Logger^ + +class Bar extends Foo: + type T = () => Logger^ + +def foo(x: Foo): x.T = + val leaked = usingLogger[x.T]: l => // error + val t: () => Logger^ = () => l + t: x.T + leaked + +def test(): Unit = + val bar = new Bar + val bad: bar.T = foo(bar) + val leaked: Logger^ = bad() // leaked scoped capability! From a8bc72a070b5874f7936791866a71405d50d9739 Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Fri, 16 Feb 2024 01:23:09 +0800 Subject: [PATCH 2/4] Improve variable naming --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 419e6454369c..3af076693dc0 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -171,9 +171,9 @@ object CheckCaptures: // Check the lower bound of path dependent types. // See issue #19330. - val isTypeParam = t.prefix eq NoPrefix + val isMember = t.prefix eq NoPrefix t.info match - case TypeBounds(lo, hi) if !isTypeParam => traverse(lo) + case TypeBounds(lo, _) if !isMember => traverse(lo) case _ => case AnnotatedType(_, ann) if ann.symbol == defn.UncheckedCapturesAnnot => () From 920bf44d4288c61625655e0a417729988b13c68b Mon Sep 17 00:00:00 2001 From: Yichen Xu Date: Fri, 16 Feb 2024 01:25:12 +0800 Subject: [PATCH 3/4] Cleanup dead code --- .../dotty/tools/dotc/cc/CheckCaptures.scala | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index 3af076693dc0..b88c46a70a4e 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -142,26 +142,6 @@ object CheckCaptures: private val seen = new EqHashSet[TypeRef] - /** Check that there is at least one method containing carrier and defined - * in the scope of tparam. E.g. this is OK: - * def f[T] = { ... var x: T ... } - * So is this: - * class C[T] { def f() = { class D { var x: T }}} - * But this is not OK: - * class C[T] { object o { var x: T }} - */ - extension (tparam: Symbol) def isParametricIn(carrier: Symbol): Boolean = - carrier.exists && { - val encl = carrier.owner.enclosingMethodOrClass - if encl.isClass then tparam.isParametricIn(encl) - else - def recur(encl: Symbol): Boolean = - if tparam.owner == encl then true - else if encl.isStatic || !encl.exists then false - else recur(encl.owner.enclosingMethodOrClass) - recur(encl) - } - def traverse(t: Type) = t.dealiasKeepAnnots match case t: TypeRef => From d539d8919650b5f3c5d5f5b123c50c8832ffeffd Mon Sep 17 00:00:00 2001 From: odersky Date: Thu, 15 Feb 2024 18:36:34 +0100 Subject: [PATCH 4/4] Apply suggestions from code review --- compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala index b88c46a70a4e..de584797f154 100644 --- a/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala +++ b/compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala @@ -151,9 +151,9 @@ object CheckCaptures: // Check the lower bound of path dependent types. // See issue #19330. - val isMember = t.prefix eq NoPrefix + val isMember = t.prefix ne NoPrefix t.info match - case TypeBounds(lo, _) if !isMember => traverse(lo) + case TypeBounds(lo, _) if isMember => traverse(lo) case _ => case AnnotatedType(_, ann) if ann.symbol == defn.UncheckedCapturesAnnot => ()